Skip to content

Commit 70270b6

Browse files
committed
Add GTK4 TextInput support
1 parent 90d5bb3 commit 70270b6

File tree

4 files changed

+45
-26
lines changed

4 files changed

+45
-26
lines changed

gtk/src/toga_gtk/keys.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,26 +187,26 @@
187187
}
188188

189189

190-
def toga_key(event):
190+
def toga_key(keyval: int, state: Gdk.ModifierType):
191191
"""Convert a GDK Key Event into a Toga key."""
192192
try:
193-
key = GDK_KEYS[event.keyval]
193+
key = GDK_KEYS[keyval]
194194
except KeyError: # pragma: no cover
195195
# Ignore any key event code we can't map. This can happen for weird key
196196
# combination (ctrl-alt-tux), and if the X server has weird key
197197
# bindings. If we can't map it, we can't really type it either, so we
198198
# need to no-cover this branch.
199199
return None
200200

201-
modifiers = set()
201+
modifiers: set[Key] = set()
202202

203-
if event.state & Gdk.ModifierType.SHIFT_MASK:
203+
if state & Gdk.ModifierType.SHIFT_MASK:
204204
modifiers.add(Key.SHIFT)
205-
if event.state & Gdk.ModifierType.CONTROL_MASK:
205+
if state & Gdk.ModifierType.CONTROL_MASK:
206206
modifiers.add(Key.MOD_1)
207-
if event.state & Gdk.ModifierType.META_MASK:
207+
if state & Gdk.ModifierType.META_MASK:
208208
modifiers.add(Key.MOD_2)
209-
if event.state & Gdk.ModifierType.HYPER_MASK:
209+
if state & Gdk.ModifierType.HYPER_MASK:
210210
modifiers.add(Key.MOD_3)
211211

212212
return {"key": key, "modifiers": modifiers}

gtk/src/toga_gtk/widgets/textinput.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,40 @@ def create(self):
1717
self.native.connect("focus-out-event", self.gtk_focus_out_event)
1818
self.native.connect("key-press-event", self.gtk_key_press_event)
1919
else: # pragma: no-cover-if-gtk3
20-
pass
20+
self.native.connect("changed", self.gtk_on_change)
21+
22+
self.focus_controller = Gtk.EventControllerFocus.new()
23+
self.native.add_controller(self.focus_controller)
24+
25+
self.focus_controller.connect("enter", self.gtk_focus_in_event)
26+
self.focus_controller.connect("leave", self.gtk_focus_out_event)
27+
28+
self.key_controller = Gtk.EventControllerKey.new()
29+
self.native.add_controller(self.key_controller)
30+
self.key_controller.connect("key-pressed", self.gtk_key_pressed)
2131

2232
def gtk_on_change(self, *_args):
23-
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
24-
self.interface._value_changed()
25-
else: # pragma: no-cover-if-gtk3
26-
self.interface._value_changed(self.interface)
33+
self.interface._value_changed()
2734

2835
def gtk_focus_in_event(self, *_args):
29-
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
30-
self.interface.on_gain_focus()
31-
else: # pragma: no-cover-if-gtk3
32-
self.interface.on_gain_focus(self.interface)
36+
self.interface.on_gain_focus()
3337

3438
def gtk_focus_out_event(self, *_args):
3539
self.interface.on_lose_focus()
3640

37-
def gtk_key_press_event(self, _, key_val, *_args):
38-
key_pressed = toga_key(key_val)
39-
if key_pressed and key_pressed["key"] in {Key.ENTER, Key.NUMPAD_ENTER}:
40-
self.interface.on_confirm()
41+
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4
42+
43+
def gtk_key_press_event(self, _entry, event):
44+
key_pressed = toga_key(event.keyval, event.state)
45+
if key_pressed and key_pressed["key"] in {Key.ENTER, Key.NUMPAD_ENTER}:
46+
self.interface.on_confirm()
47+
48+
else: # pragma: no-cover-if-gtk3
49+
50+
def gtk_key_pressed(self, _controller, keyval, _keycode, state):
51+
key_pressed = toga_key(keyval, state)
52+
if key_pressed and key_pressed["key"] in {Key.ENTER, Key.NUMPAD_ENTER}:
53+
self.interface.on_confirm()
4154

4255
def get_readonly(self):
4356
return not self.native.get_property("editable")
@@ -81,7 +94,16 @@ def rehint(self):
8194
)
8295
self.interface.intrinsic.height = height[1]
8396
else: # pragma: no-cover-if-gtk3
84-
pass
97+
# print(
98+
# "REHINT",
99+
# self,
100+
# self.native.get_preferred_size()[0].width,
101+
# self.native.get_preferred_size()[0].height,
102+
# )
103+
min_size, size = self.native.get_preferred_size()
104+
105+
self.interface.intrinsic.width = at_least(min_size.width)
106+
self.interface.intrinsic.height = size.height
85107

86108
def set_error(self, error_message):
87109
self.native.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "error")

gtk/tests_backend/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def keystroke(self, combination):
237237
event.is_modifier = state != 0
238238
event.state = state
239239

240-
return toga_key(event)
240+
return toga_key(event.keyval, event.state)
241241

242242
async def restore_standard_app(self):
243243
# No special handling needed to restore standard app.

gtk/tests_backend/widgets/textinput.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33
from toga.constants import JUSTIFY, LEFT
4-
from toga_gtk.libs import GTK_VERSION, Gtk
4+
from toga_gtk.libs import Gtk
55

66
from .base import SimpleProbe
77
from .properties import toga_x_text_align
@@ -10,9 +10,6 @@
1010
class TextInputProbe(SimpleProbe):
1111
native_class = Gtk.Entry
1212

13-
if GTK_VERSION >= (4, 0, 0):
14-
pytest.skip("GTK4 doesn't support text input yet")
15-
1613
@property
1714
def value(self):
1815
return (

0 commit comments

Comments
 (0)