diff --git a/examples/miscellaneous/on_textedit_event.py b/examples/miscellaneous/on_textedit_event.py index c361f0774..bb6c426fe 100644 --- a/examples/miscellaneous/on_textedit_event.py +++ b/examples/miscellaneous/on_textedit_event.py @@ -18,9 +19,44 @@ class TextInputIME(TextInput): def __init__(self, **kwargs): super(TextInputIME, self).__init__(**kwargs) EventLoop.window.bind(on_textedit=self._on_textedit) + self._editing = False - def _on_textedit(self, window, text): + def _on_textedit(self, window, text, cursor, target, target_end, + candshow, candidates, candsel): self.testtext = text + self._editing = True if len(text) else False + + text = "{}[u]{}[/u]{}".format(text[:target], + text[target:target_end], + text[target_end:]) + + # if IME has no target, the candidates are predicted sentences + # which will be selected when the user presses tab + completion = True if target_end == target else False + + if candshow: + candlist = [] + for i in range(10): + cand = candidates[i] + if len(cand): + if i == candsel: + cand = "[u]{}[/u]".format(cand) + candlist.append(cand) + candstr = "" + if len(candlist): + candstr = " ({})".format(" / ".join(candlist)) + if completion: + candstr = "[sub]tab:{}[/sub]".format(candstr) + text += candstr + + self.suggestion_text = text + + def keyboard_on_key_down(self, window, keycode, text, modifiers): + # IME consumes it all + if self._editing: + return True + super(TextInputIME, self).keyboard_on_key_down( + window, keycode, text, modifiers) class MainWidget(Widget): @@ -86,6 +122,7 @@ if __name__ == '__main__': id: text_box size_hint_x: 7 focus: True + text: " " # hack to make sure the textinput has a line BoxLayout: Button: size_hint_x: 3 diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py index ba1df7fc8..898ffce07 100755 --- a/kivy/core/window/__init__.py +++ b/kivy/core/window/__init__.py @@ -321,10 +321,13 @@ class WindowBase(EventDispatcher): .. versionadded:: 1.9.0 - `on_textedit(self, text)`: + `on_textedit(self, text, start, length, target_start, target_length)`: Fired when inputting with IME. The string inputting with IME is set as the parameter of - this event. + this event. The position of cursor is set as start. If a + reading string is included in the text, its length is set. + If a part of the text is being edited, its starting point + and its length are also set. .. versionadded:: 1.10.1 ''' @@ -1806,10 +1809,14 @@ class WindowBase(EventDispatcher): ''' pass - def on_textedit(self, text): + def on_textedit(self, text, cursor, target_start, target_end, + candshow, candidates, candsel): '''Event called when inputting with IME. The string inputting with IME is set as the parameter of - this event. + this event. The position of cursor is also set. The range + of the target is set. The candidate list is set as + candidates, which is to be rendered if candshow is true. + The current selection number is candsel. .. versionadded:: 1.10.1 ''' diff --git a/kivy/core/window/_window_sdl2.pyx b/kivy/core/window/_window_sdl2.pyx index 7bbc1767b..9c414657f 100644 --- a/kivy/core/window/_window_sdl2.pyx +++ b/kivy/core/window/_window_sdl2.pyx @@ -238,6 +238,9 @@ cdef class _WindowSDL2Storage: SDL_SetEventFilter(_event_filter, self) + SDL_EventState(SDL_TEXTEDITINGEX, SDL_ENABLE) + SDL_EventState(SDL_TEXTEDITING, SDL_IGNORE) + SDL_EventState(SDL_DROPFILE, SDL_ENABLE) cdef int w, h SDL_GetWindowSize(self.win, &w, &h) @@ -513,12 +516,14 @@ cdef class _WindowSDL2Storage: SDL_SetTextInputRect(rect) SDL_StartTextInput() + SDL_EventState(SDL_TEXTEDITINGEX, SDL_ENABLE) finally: PyMem_Free(rect) def hide_keyboard(self): if SDL_IsTextInputActive(): SDL_StopTextInput() + SDL_EventState(SDL_TEXTEDITINGEX, SDL_DISABLE) def is_keyboard_shown(self): return SDL_IsTextInputActive() @@ -644,9 +649,22 @@ cdef class _WindowSDL2Storage: elif event.type == SDL_TEXTINPUT: s = event.text.text.decode('utf-8') return ('textinput', s) - elif event.type == SDL_TEXTEDITING: - s = event.edit.text.decode('utf-8') - return ('textedit', s) + elif event.type == SDL_TEXTEDITINGEX: + composition = event.editx.composition.decode('utf-8') + SDL_free(event.editx.composition) + commit = event.editx.commit + cursor = event.editx.cursor + target_start = event.editx.target_start + target_end = event.editx.target_end + candshow = event.editx.candshow + candidates = [] + if event.editx.candidates != NULL: + for i in range(10): + item = event.editx.candidates[(i * 512):(i * 512 + 512)] + candidates.append(item.decode('utf-8', 'ignore').partition('\0')[0]) + SDL_free(event.editx.candidates) + candsel = event.editx.candsel + return ('texteditx', composition, commit, cursor, target_start, target_end, candshow, candidates, candsel) else: # print('receive unknown sdl window event', event.type) pass diff --git a/kivy/core/window/window_sdl2.py b/kivy/core/window/window_sdl2.py index 2b653d238..eaa265086 100644 --- a/kivy/core/window/window_sdl2.py +++ b/kivy/core/window/window_sdl2.py @@ -686,11 +686,28 @@ class WindowSDL(WindowBase): elif action == 'textinput': text = args[0] - self.dispatch('on_textinput', text) - - elif action == 'textedit': - text = args[0] - self.dispatch('on_textedit', text) + # possibly the text was trimmed + # the complete text is sent via texteditx + if len(text.encode("utf-8")) < 30: + self.dispatch('on_textinput', text) + + elif action == 'texteditx': + composition = args[0] + commit = args[1] + cursor = args[2] + target_start = args[3] + target_end = args[4] + candshow = args[5] + candidates = args[6] + candsel = args[7] + if commit: + if len(composition.encode("utf-8")) > 29: + self.dispatch('on_textinput', composition) + # else: already dispatched via textinput event + else: + self.dispatch('on_textedit', composition, cursor, + target_start, target_end, + candshow, candidates, candsel) # unhandled event ! else: diff --git a/kivy/lib/sdl2.pxi b/kivy/lib/sdl2.pxi index f750e5d3e..d74904b92 100644 --- a/kivy/lib/sdl2.pxi +++ b/kivy/lib/sdl2.pxi @@ -15,6 +15,7 @@ cdef extern from "SDL_joystick.h": cdef extern from "SDL.h": ctypedef unsigned char Uint8 + ctypedef signed char Sint8 ctypedef unsigned long Uint32 ctypedef signed long Sint32 ctypedef unsigned long long Uint64 @@ -166,6 +167,7 @@ cdef extern from "SDL.h": SDL_KEYDOWN = 0x300 SDL_KEYUP SDL_TEXTEDITING + SDL_TEXTEDITINGEX SDL_TEXTINPUT SDL_MOUSEMOTION = 0x400 SDL_MOUSEBUTTONDOWN = 0x401 @@ -318,8 +320,21 @@ cdef extern from "SDL.h": Uint32 timestamp Uint32 windowID # The window with keyboard focus, if any */ char *text # The editing text */ - Sint32 start # The start cursor of selected editing text */ - Sint32 length # The length of selected editing text */ + Sint32 start # The cursor, where reading text starts if any */ + Sint32 length # The length of reading text */ + + cdef struct SDL_TextEditingExEvent: + Uint32 type # ::SDL_TEXTEDITINGEX */ + Uint32 timestamp + Uint32 windowID # The window with keyboard focus, if any */ + char* composition # The editing text, needs freeing */ + SDL_bool commit # Whether the event should be treated as TEXTINPUT */ + Uint16 cursor # The cursor position */ + Uint16 target_start # The starting point of editing part */ + Uint16 target_end # The length of editing part */ + SDL_bool candshow # Whether to show candidate list */ + char* candidates # The candidate array[MAX_CANDLIST], needs freeing */ + Sint8 candsel # The cursor of the candidate list */ cdef struct SDL_TextInputEvent: Uint32 type # ::SDL_TEXTINPUT */ @@ -381,6 +396,7 @@ cdef extern from "SDL.h": SDL_WindowEvent window SDL_KeyboardEvent key SDL_TextEditingEvent edit + SDL_TextEditingExEvent editx SDL_TextInputEvent text SDL_MouseMotionEvent motion SDL_MouseButtonEvent button @@ -648,6 +664,8 @@ cdef extern from "SDL.h": Uint16 SDL_LIL_ENDIAN Uint16 SDL_BIG_ENDIAN + cdef void SDL_free(void *mem) + cdef extern from "SDL_shape.h": cdef SDL_Window * SDL_CreateShapedWindow( char *title, diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 056b67fe0..f751c6025 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -3022,19 +3022,20 @@ class TextInput(FocusBehavior, Widget): if cursor_row >= len(self._lines) or self.canvas is None: return - cursor_pos = self.cursor_pos + cursor_col = self.cursor_col txt = self._lines[cursor_row] + pre = txt[:cursor_col] + suf = txt[cursor_col:] kw = self._get_line_options() rct = self._lines_rects[cursor_row] - lbl = text = None + lbl = None if value: lbl = MarkupLabel( - text=txt + "[b]{}[/b]".format(value), **kw) + text="{}[b]{}[/b]{}".format(pre, value, suf), **kw) else: - lbl = Label(**kw) - text = txt + lbl = Label(text=txt, **kw) lbl.refresh()