From 8ee1c7580c59c0ccb6b0a66b0dc400740016eaf3 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sat, 3 May 2014 20:44:30 -0400 Subject: [PATCH 01/22] Change TextInput to inherit from FocusBehavior and letting it handle all the keyboard binding/releasing. --- kivy/uix/textinput.py | 178 +++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 96 deletions(-) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index c07f869fc..5fbc989de 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -151,6 +151,7 @@ from kivy.compat import PY2 from kivy.logger import Logger from kivy.metrics import inch from kivy.utils import boundary, platform +from kivy.uix.behaviors import FocusBehavior from kivy.core.text import Label from kivy.graphics import Color, Rectangle @@ -314,7 +315,7 @@ class TextInputCutCopyPaste(Bubble): anim.start(self) -class TextInput(Widget): +class TextInput(FocusBehavior, Widget): '''TextInput class. See module documentation for more information. :Events: @@ -346,6 +347,10 @@ class TextInput(Widget): on the next clock cycle using :meth:`~kivy.clock.ClockBase.schedule_once`. + .. versionchanged:: 1.8.1 + :class:`TextInput` now inherits from + :class:`~kivy.uix.behaviors.FocusBehavior`. + .. versionchanged:: 1.7.0 `on_double_tap`, `on_triple_tap` and `on_quad_touch` events added. ''' @@ -375,7 +380,6 @@ class TextInput(Widget): self._hint_text_rects = [] self._label_cached = None self._line_options = None - self._keyboard = None self._keyboard_mode = Config.get('kivy', 'keyboard_mode') self._command_mode = False self._command = '' @@ -402,6 +406,12 @@ class TextInput(Widget): self.bind(font_size=self._trigger_refresh_line_options, font_name=self._trigger_refresh_line_options) + def set_focused(instance, value): + self.focused = value + + def set_focus(instance, value): + self.focus = value + self.bind(padding=self._update_text_options, tab_width=self._update_text_options, font_size=self._update_text_options, @@ -409,7 +419,8 @@ class TextInput(Widget): size=self._update_text_options, password=self._update_text_options) - self.bind(pos=self._trigger_update_graphics) + self.bind(pos=self._trigger_update_graphics, focus=set_focused, + focused=set_focus) self._trigger_position_handles = Clock.create_trigger( self._position_handles) @@ -424,10 +435,6 @@ class TextInput(Widget): # when the gl context is reloaded, trigger the text rendering again. _textinput_list.append(ref(self, TextInput._reload_remove_observer)) - def on_disabled(self, instance, value): - if value: - self.focus = False - def on_text_validate(self): pass @@ -961,14 +968,9 @@ class TextInput(Widget): touch_pos = touch.pos if not self.collide_point(*touch_pos): - if self._keyboard_mode == 'multi': - if self.readonly: - self.focus = False - else: - self.focus = False return False - if not self.focus: - self.focus = True + if super(TextInput, self).on_touch_down(touch): + return True # Check for scroll wheel if 'button' in touch.profile and touch.button.startswith('scroll'): @@ -1298,76 +1300,23 @@ class TextInput(Widget): if wr in _textinput_list: _textinput_list.remove(wr) - def _set_window(self, *largs): - win = self._win - if not win: - self._win = win = EventLoop.window - if not win: - # we got argument, it could be the previous schedule - # cancel focus. - if len(largs): - Logger.warning('Textinput: ' - 'Cannot focus the element, unable to get ' - 'root window') - return - else: - #XXX where do `value` comes from? - Clock.schedule_once(partial(self.on_focus, self, largs), 0) - return - - def on_focus(self, instance, value, *largs): - self._set_window(*largs) - + def on_focused(self, instance, value, *largs): if value: - if self.keyboard_mode != 'managed': - self._bind_keyboard() + if (not (self.readonly or self.disabled) or _is_desktop and + self._keyboard_mode == 'system'): + Clock.schedule_interval(self._do_blink_cursor, 1 / 2.) + self._editable = True + else: + self._editable = False else: - if self.keyboard_mode != 'managed': - self._unbind_keyboard() - - def _unbind_keyboard(self): - self._set_window() - win = self._win - if self._keyboard: - keyboard = self._keyboard - keyboard.unbind( - on_key_down=self._keyboard_on_key_down, - on_key_up=self._keyboard_on_key_up) - keyboard.release() - self._keyboard = None - - self.cancel_selection() - Clock.unschedule(self._do_blink_cursor) - self._hide_cut_copy_paste(win) - self._hide_handles(win) - self._win = None - - def _bind_keyboard(self): - self._set_window() - win = self._win - self._editable = editable = ( - not (self.readonly or self.disabled) or - _is_desktop and self._keyboard_mode == 'system') - - if not _is_desktop and not editable: - return - - keyboard = win.request_keyboard( - self._keyboard_released, self, input_type=self.input_type) - self._keyboard = keyboard - if editable: - keyboard.bind( - on_key_down=self._keyboard_on_key_down, - on_key_up=self._keyboard_on_key_up) - Clock.schedule_interval(self._do_blink_cursor, 1 / 2.) - else: - # in non-editable mode, we still want shortcut (as copy) - keyboard.bind( - on_key_down=self._keyboard_on_key_down) - - def on_readonly(self, instance, value): - if not value: - self.focus = False + win = self._win + if not win: + self._win = win = EventLoop.window + self.cancel_selection() + Clock.unschedule(self._do_blink_cursor) + self._hide_cut_copy_paste(win) + self._hide_handles(win) + self._win = None def _ensure_clipboard(self): global Clipboard @@ -1414,12 +1363,6 @@ class TextInput(Widget): self.delete_selection() self.insert_text(data) - def _keyboard_released(self): - # Callback called when the real keyboard is taken by someone else - # called by the window if the keyboard is taken by somebody else - # FIXME: handle virtual keyboard. - self.focus = False - def _get_text_width(self, text, tab_width, _label_cached): # Return the width of a text, according to the current line options kw = self._get_line_options() @@ -1962,7 +1905,7 @@ class TextInput(Widget): if self._selection: self._update_selection(True) - def _keyboard_on_key_down(self, window, keycode, text, modifiers): + def keyboard_on_key_down(self, window, keycode, text, modifiers): # Keycodes on OSX: ctrl, cmd = 64, 1024 key, key_str = keycode @@ -1973,13 +1916,17 @@ class TextInput(Widget): _is_osx and modifiers == ['meta'])) is_interesting_key = key in (list(self.interesting_keys.keys()) + [27]) + if not self.write_tab and super(TextInput, + self).keyboard_on_key_down(window, keycode, text, modifiers): + return True + if not self._editable: # duplicated but faster testing for non-editable keys if text and not is_interesting_key: if is_shortcut and key == ord('c'): self.copy() elif key == 27: - self.focus = False + self.focused = False return True if text and not is_interesting_key: @@ -2060,7 +2007,7 @@ class TextInput(Widget): self._hide_handles(win) if key == 27: # escape - self.focus = False + self.focused = False return True elif key == 9: # tab self.insert_text(u'\t') @@ -2071,7 +2018,7 @@ class TextInput(Widget): key = (None, None, k, 1) self._key_down(key) - def _keyboard_on_key_up(self, window, keycode): + def keyboard_on_key_up(self, window, keycode): key, key_str = keycode k = self.interesting_keys.get(key) if k: @@ -2496,6 +2443,12 @@ class TextInput(Widget): show selection when TextInput is focused, you should delay (use Clock.schedule) the call to the functions for selecting text (select_all, select_text). + + ..versionchanged:: 1.8.1 + :class:`TextInput` now inherits from + :class:`~kivy.uix.behaviors.FocusBehavior` and :attr:`focus` is now + an alias for :attr:`focused`. Setting either one will also set the + other. ''' def _get_text(self, encode=True): @@ -2710,21 +2663,54 @@ class TextInput(Widget): :attr:`keyboard_mode` is an :class:`~kivy.properties.OptionsProperty` and defaults to 'auto'. Can be one of 'auto' or 'managed'. + + .. versionchanged:: 1.8.1 + This property doesn't control the keyboard anymore. + :class:`TextInput` now inherits from + :class:`~kivy.uix.behaviors.FocusBehavior` which uses the + :attr:`keyboard` attribute for similar functionality. + + This property will be removed in 2.0. ''' def show_keyboard(self): """ Convenience function to show the keyboard in managed mode + + .. versionchanged:: 1.8.1 + This method doesn't control the keyboard anymore. + :class:`TextInput` now inherits from + :class:`~kivy.uix.behaviors.FocusBehavior` which uses the + :attr:`keyboard` attribute for similar functionality. + + This method will be removed in 2.0. """ - if self.keyboard_mode == "managed": - self._bind_keyboard() + pass def hide_keyboard(self): """ Convenience function to hide the keyboard in managed mode + + .. versionchanged:: 1.8.1 + This method doesn't control the keyboard anymore. + :class:`TextInput` now inherits from + :class:`~kivy.uix.behaviors.FocusBehavior` which uses the + :attr:`keyboard` attribute for similar functionality. + + This method will be removed in 2.0. """ - if self.keyboard_mode == "managed": - self._unbind_keyboard() + pass + + write_tab = BooleanProperty(True) + '''Whether the tab key should move focus to the next widget or if it should + enter a tab in the :class:`TextInput`. If `True` a tab will be written, + otherwise, focus will move to the next widget. + + .. versionadded:: 1.8.1 + + :attr:`write_tab` is a :class:`~kivy.properties.BooleanProperty` and + defaults to `True`. + ''' if __name__ == '__main__': From 906ae3a7cbfc165671d3354ae286bab5f82a9be2 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sat, 3 May 2014 20:53:13 -0400 Subject: [PATCH 02/22] Ensure that clicking outside a focused widget will remove focus from the widget. --- kivy/uix/behaviors.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 83ce4d7e2..68a45be5a 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -675,8 +675,6 @@ class FocusBehavior(object): self.bind(focused=self._on_focused, disabled=self._on_focusable, is_focusable=self._on_focusable, - # don't be at mercy of child calling super - on_touch_down=self._focus_on_touch_down, focus_next=self._set_on_focus_next, focus_previous=self._set_on_focus_previous) @@ -739,11 +737,11 @@ class FocusBehavior(object): self.focused = False def _focus_on_touch_down(self, instance, touch): - if not self.disabled and self.is_focusable and\ - self.collide_point(*touch.pos) and ('button' not in touch.profile - or not touch.button.startswith('scroll')): - self.focused = True - return False + pos = self.to_widget(*touch.pos) + if (not self.disabled and self.is_focusable and + ('button' not in touch.profile or + not touch.button.startswith('scroll'))): + self.focused = self.collide_point(*pos) def _get_focus_next(self, focus_dir): current = self From 482485c4fa7a1ecb163aa888fcc290f303a497ee Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Mon, 5 May 2014 12:39:43 -0400 Subject: [PATCH 03/22] Fix kivycatalog to use focused TextInput method. --- examples/demo/kivycatalog/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/demo/kivycatalog/main.py b/examples/demo/kivycatalog/main.py index 2ee3fb7d6..e6e180f21 100755 --- a/examples/demo/kivycatalog/main.py +++ b/examples/demo/kivycatalog/main.py @@ -57,7 +57,7 @@ for class_name in CONTAINER_CLASSES: class KivyRenderTextInput(CodeInput): - def _keyboard_on_key_down(self, window, keycode, text, modifiers): + def keyboard_on_key_down(self, window, keycode, text, modifiers): is_osx = sys.platform == 'darwin' # Keycodes on OSX: ctrl, cmd = 64, 1024 @@ -70,7 +70,7 @@ class KivyRenderTextInput(CodeInput): self.catalog.change_kv(True) return - super(KivyRenderTextInput, self)._keyboard_on_key_down( + super(KivyRenderTextInput, self).keyboard_on_key_down( window, keycode, text, modifiers) From 51404775005eaaf24edeb1fae4aecb6b34b4be68 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Mon, 5 May 2014 12:41:47 -0400 Subject: [PATCH 04/22] Rename focus behavior _win var to prevent name clash with TextInput. --- kivy/uix/behaviors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 68a45be5a..c77cd5a94 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -492,7 +492,7 @@ class FocusBehavior(object): future version. ''' - _win = None + _focus_win = None _requested_keyboard = False _keyboard = ObjectProperty(None, allownone=True) _keyboards = {} @@ -690,9 +690,9 @@ class FocusBehavior(object): def _ensure_keyboard(self): if self._keyboard is None: - win = self._win + win = self._focus_win if not win: - self._win = win = EventLoop.window + self._focus_win = win = EventLoop.window if not win: Logger.warning('FocusBehavior: ' 'Cannot focus the element, unable to get root window') From 078b945f5cdb2616db8cc0e3db06b6070d83703b Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Mon, 5 May 2014 14:36:47 -0400 Subject: [PATCH 05/22] Make text input focusable by default, even on mobile. --- kivy/uix/textinput.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 5fbc989de..60443ec11 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -359,6 +359,7 @@ class TextInput(FocusBehavior, Widget): 'on_quad_touch') def __init__(self, **kwargs): + self.is_focusable = kwargs.get('is_focusable', True) self._win = None self._cursor_blink_time = Clock.get_time() self._cursor = [0, 0] From 4b7b5ff1972a5cd95822a3c6bdb59feb2d88fe6c Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Tue, 13 May 2014 11:37:29 -0400 Subject: [PATCH 06/22] When readonly, don't focus when not on desktop. --- kivy/uix/textinput.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 60443ec11..695f018f5 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -413,6 +413,10 @@ class TextInput(FocusBehavior, Widget): def set_focus(instance, value): self.focus = value + def handle_readonly(instance, value): + if value and not _is_desktop: + self.is_focusable = False + self.bind(padding=self._update_text_options, tab_width=self._update_text_options, font_size=self._update_text_options, @@ -421,7 +425,8 @@ class TextInput(FocusBehavior, Widget): password=self._update_text_options) self.bind(pos=self._trigger_update_graphics, focus=set_focused, - focused=set_focus) + focused=set_focus, readonly=handle_readonly) + handle_readonly(self, self.readonly) self._trigger_position_handles = Clock.create_trigger( self._position_handles) From 3f7c7a067b8d4b7b5dec6e1757c556b3a2d720c1 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Tue, 13 May 2014 13:23:46 -0400 Subject: [PATCH 07/22] Add keyboard_mode to focus behavior. --- kivy/uix/behaviors.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index c77cd5a94..bee91b953 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -668,6 +668,16 @@ class FocusBehavior(object): defaults to `None`. ''' + keyboard_mode = OptionProperty('auto', options=('auto', 'managed')) + '''How the keyboard visibility should be managed (auto will have standard + behaviour to show/hide on focus, managed requires setting keyboard_visible + manually, or calling the helper functions ``show_keyboard()`` + and ``hide_keyboard()``. + + :attr:`keyboard_mode` is an :class:`~kivy.properties.OptionsProperty` and + defaults to 'auto'. Can be one of 'auto' or 'managed'. + ''' + def __init__(self, **kwargs): self._old_focus_next = None self._old_focus_previous = None @@ -683,10 +693,11 @@ class FocusBehavior(object): self.focused = False def _on_focused(self, instance, value, *largs): - if value: - self._bind_keyboard() - else: - self._unbind_keyboard() + if self.keyboard_mode == 'auto': + if value: + self._bind_keyboard() + else: + self._unbind_keyboard() def _ensure_keyboard(self): if self._keyboard is None: @@ -821,6 +832,20 @@ class FocusBehavior(object): return True return False + def show_keyboard(self): + ''' + Convenience function to show the keyboard in managed mode. + ''' + if self.keyboard_mode == 'managed': + self._bind_keyboard() + + def hide_keyboard(self): + ''' + Convenience function to hide the keyboard in managed mode. + ''' + if self.keyboard_mode == 'managed': + self._unbind_keyboard() + class CompoundSelectionBehavior(object): '''Selection behavior implements the logic behind keyboard and touch From 195ee949240cd1398f1dbf61349ee7d655158c2d Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Tue, 13 May 2014 13:25:11 -0400 Subject: [PATCH 08/22] use allow_copy. --- kivy/uix/textinput.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 695f018f5..7e8e0ee8a 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -414,7 +414,7 @@ class TextInput(FocusBehavior, Widget): self.focus = value def handle_readonly(instance, value): - if value and not _is_desktop: + if value and (not _is_desktop or not self.allow_copy): self.is_focusable = False self.bind(padding=self._update_text_options, From 0e86c9ed1764ac31e060a4dedcacc0661a5d6c22 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Tue, 13 May 2014 13:27:05 -0400 Subject: [PATCH 09/22] Update textinput keyboard_mode to indicate we also inherit it from focusbehavior. --- kivy/uix/textinput.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 7e8e0ee8a..30f0b2fc6 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -2671,12 +2671,11 @@ class TextInput(FocusBehavior, Widget): defaults to 'auto'. Can be one of 'auto' or 'managed'. .. versionchanged:: 1.8.1 - This property doesn't control the keyboard anymore. - :class:`TextInput` now inherits from - :class:`~kivy.uix.behaviors.FocusBehavior` which uses the - :attr:`keyboard` attribute for similar functionality. + This property is redundant because :class:`TextInput` now inherits from + :class:`~kivy.uix.behaviors.FocusBehavior` which has its own + :attr:`~kivy.uix.behaviors.FocusBehavior.keyboard_mode`. - This property will be removed in 2.0. + This property will be removed from the :class:`TextInput` in 2.0. ''' def show_keyboard(self): @@ -2684,28 +2683,28 @@ class TextInput(FocusBehavior, Widget): Convenience function to show the keyboard in managed mode .. versionchanged:: 1.8.1 - This method doesn't control the keyboard anymore. - :class:`TextInput` now inherits from - :class:`~kivy.uix.behaviors.FocusBehavior` which uses the - :attr:`keyboard` attribute for similar functionality. - This method will be removed in 2.0. + This method is redundant because :class:`TextInput` now inherits + from :class:`~kivy.uix.behaviors.FocusBehavior` which has its own + :meth:`~kivy.uix.behaviors.FocusBehavior.show_keyboard`. + + This property will be removed from the :class:`TextInput` in 2.0. """ - pass + super(TextInput, self).show_keyboard() def hide_keyboard(self): """ Convenience function to hide the keyboard in managed mode .. versionchanged:: 1.8.1 - This method doesn't control the keyboard anymore. - :class:`TextInput` now inherits from - :class:`~kivy.uix.behaviors.FocusBehavior` which uses the - :attr:`keyboard` attribute for similar functionality. - This method will be removed in 2.0. + This method is redundant because :class:`TextInput` now inherits + from :class:`~kivy.uix.behaviors.FocusBehavior` which has its own + :meth:`~kivy.uix.behaviors.FocusBehavior.hide_keyboard`. + + This property will be removed from the :class:`TextInput` in 2.0. """ - pass + super(TextInput, self).hide_keyboard() write_tab = BooleanProperty(True) '''Whether the tab key should move focus to the next widget or if it should From 4959fabce15da703c43e913c30e79538c52dc531 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Tue, 13 May 2014 23:21:39 -0400 Subject: [PATCH 10/22] Remove keyboard_mode from TextInput. --- kivy/uix/textinput.py | 52 +++++-------------------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 30f0b2fc6..918e5ed4d 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -347,6 +347,11 @@ class TextInput(FocusBehavior, Widget): on the next clock cycle using :meth:`~kivy.clock.ClockBase.schedule_once`. + .. note:: + `keyboard_mode`, `show_keyboard`, and `hide_keyboard` have been removed + from :class:`TextInput` since they are now inherited from + :class:`~kivy.uix.behaviors.FocusBehavior`. + .. versionchanged:: 1.8.1 :class:`TextInput` now inherits from :class:`~kivy.uix.behaviors.FocusBehavior`. @@ -2659,53 +2664,6 @@ class TextInput(FocusBehavior, Widget): if self._handle_right: self._handle_right.source = value - keyboard_mode = OptionProperty('auto', options=('auto', 'managed')) - '''How the keyboard visibility should be managed (auto will have standard - behaviour to show/hide on focus, managed requires setting keyboard_visible - manually, or calling the helper functions ``show_keyboard()`` - and ``hide_keyboard()``. - - .. versionadded:: 1.8.0 - - :attr:`keyboard_mode` is an :class:`~kivy.properties.OptionsProperty` and - defaults to 'auto'. Can be one of 'auto' or 'managed'. - - .. versionchanged:: 1.8.1 - This property is redundant because :class:`TextInput` now inherits from - :class:`~kivy.uix.behaviors.FocusBehavior` which has its own - :attr:`~kivy.uix.behaviors.FocusBehavior.keyboard_mode`. - - This property will be removed from the :class:`TextInput` in 2.0. - ''' - - def show_keyboard(self): - """ - Convenience function to show the keyboard in managed mode - - .. versionchanged:: 1.8.1 - - This method is redundant because :class:`TextInput` now inherits - from :class:`~kivy.uix.behaviors.FocusBehavior` which has its own - :meth:`~kivy.uix.behaviors.FocusBehavior.show_keyboard`. - - This property will be removed from the :class:`TextInput` in 2.0. - """ - super(TextInput, self).show_keyboard() - - def hide_keyboard(self): - """ - Convenience function to hide the keyboard in managed mode - - .. versionchanged:: 1.8.1 - - This method is redundant because :class:`TextInput` now inherits - from :class:`~kivy.uix.behaviors.FocusBehavior` which has its own - :meth:`~kivy.uix.behaviors.FocusBehavior.hide_keyboard`. - - This property will be removed from the :class:`TextInput` in 2.0. - """ - super(TextInput, self).hide_keyboard() - write_tab = BooleanProperty(True) '''Whether the tab key should move focus to the next widget or if it should enter a tab in the :class:`TextInput`. If `True` a tab will be written, From a16acc10e1bf6925d3542994a1ade2811f48c6b6 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Tue, 13 May 2014 23:41:48 -0400 Subject: [PATCH 11/22] Only unfocus when clicking outside focusable if keyboard mode is not multi. --- kivy/uix/behaviors.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index bee91b953..7f37c5d1a 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -683,6 +683,7 @@ class FocusBehavior(object): self._old_focus_previous = None super(FocusBehavior, self).__init__(**kwargs) + self._keyboard_mode = Config.get('kivy', 'keyboard_mode') self.bind(focused=self._on_focused, disabled=self._on_focusable, is_focusable=self._on_focusable, focus_next=self._set_on_focus_next, @@ -752,7 +753,10 @@ class FocusBehavior(object): if (not self.disabled and self.is_focusable and ('button' not in touch.profile or not touch.button.startswith('scroll'))): - self.focused = self.collide_point(*pos) + if self.collide_point(*pos): + self.focused = True + elif self._keyboard_mode != 'multi': + self.focused = False def _get_focus_next(self, focus_dir): current = self From 0a3e5d5560d5434af360302a94e20c8ebeb1373a Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Wed, 14 May 2014 12:15:16 -0400 Subject: [PATCH 12/22] Add property for unfocusing when clicking outside widget. --- kivy/uix/behaviors.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 7f37c5d1a..126a82fc0 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -37,10 +37,12 @@ import string # When we are generating documentation, Config doesn't exist _scroll_timeout = _scroll_distance = 0 _is_desktop = False +_keyboard_mode = 'system' if Config: _scroll_timeout = Config.getint('widgets', 'scroll_timeout') _scroll_distance = Config.getint('widgets', 'scroll_distance') _is_desktop = Config.getboolean('kivy', 'desktop') + _keyboard_mode = Config.get('kivy', 'keyboard_mode') class ButtonBehavior(object): @@ -678,12 +680,27 @@ class FocusBehavior(object): defaults to 'auto'. Can be one of 'auto' or 'managed'. ''' + unfocus_on_touch = BooleanProperty(_keyboard_mode != 'multi') + '''Whether a instance should lose focus when clicked outside the instance. + + When a user clicks on a widget that is focus aware and shares the same + keyboard as the this widget (which in the case of only one keyboard, are + all focus aware widgets), then as the other widgets gains focus, this + widget loses focus. In addition to that, if this property is `True`, + clicking on any widget other than this widget, will remove focus form this + widget. + + :attr:`unfocus_on_touch` is a :class:`~kivy.properties.BooleanProperty`, + defaults to `False` if the `keyboard_mode` in :attr:`~kivy.config.Config` + is `'multi'`, otherwise it defaults to `True`. + ''' + def __init__(self, **kwargs): self._old_focus_next = None self._old_focus_previous = None super(FocusBehavior, self).__init__(**kwargs) - self._keyboard_mode = Config.get('kivy', 'keyboard_mode') + self._keyboard_mode = _keyboard_mode self.bind(focused=self._on_focused, disabled=self._on_focusable, is_focusable=self._on_focusable, focus_next=self._set_on_focus_next, @@ -755,7 +772,7 @@ class FocusBehavior(object): not touch.button.startswith('scroll'))): if self.collide_point(*pos): self.focused = True - elif self._keyboard_mode != 'multi': + elif self.unfocus_on_touch: self.focused = False def _get_focus_next(self, focus_dir): From 99e40400227d6632055191b99a00c5a176b7321e Mon Sep 17 00:00:00 2001 From: matham Date: Wed, 14 May 2014 13:11:04 -0400 Subject: [PATCH 13/22] Use multi and systemandmulti for unfocus_on_touch default. --- kivy/uix/behaviors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 126a82fc0..c3037c1bb 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -680,7 +680,7 @@ class FocusBehavior(object): defaults to 'auto'. Can be one of 'auto' or 'managed'. ''' - unfocus_on_touch = BooleanProperty(_keyboard_mode != 'multi') + unfocus_on_touch = BooleanProperty(_keyboard_mode not in ('multi', 'systemandmulti')) '''Whether a instance should lose focus when clicked outside the instance. When a user clicks on a widget that is focus aware and shares the same @@ -692,7 +692,7 @@ class FocusBehavior(object): :attr:`unfocus_on_touch` is a :class:`~kivy.properties.BooleanProperty`, defaults to `False` if the `keyboard_mode` in :attr:`~kivy.config.Config` - is `'multi'`, otherwise it defaults to `True`. + is `'multi'` or `'systemandmulti'`, otherwise it defaults to `True`. ''' def __init__(self, **kwargs): From 2ca49f1e28efd5f84829677202f3b1f6e10fa20d Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Mon, 2 Jun 2014 14:17:19 -0400 Subject: [PATCH 14/22] Remove circular setting of focus/unfocus. With tab focus cycling, don't select disabled focusable. --- kivy/uix/behaviors.py | 4 ++-- kivy/uix/textinput.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index c3037c1bb..cbbd8d48d 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -785,7 +785,7 @@ class FocusBehavior(object): current = getattr(current, focus_dir) if current is self or current is StopIteration: return None # make sure we don't loop forever - if current.is_focusable: + if current.is_focusable and not current.disabled: return current # hit unfocusable, walk widget tree @@ -799,7 +799,7 @@ class FocusBehavior(object): if isinstance(current, FocusBehavior): if current is self: return None - if current.is_focusable: + if current.is_focusable and not current.disabled: return current else: return None diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 918e5ed4d..a17bcbaf2 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -415,9 +415,6 @@ class TextInput(FocusBehavior, Widget): def set_focused(instance, value): self.focused = value - def set_focus(instance, value): - self.focus = value - def handle_readonly(instance, value): if value and (not _is_desktop or not self.allow_copy): self.is_focusable = False @@ -430,7 +427,7 @@ class TextInput(FocusBehavior, Widget): password=self._update_text_options) self.bind(pos=self._trigger_update_graphics, focus=set_focused, - focused=set_focus, readonly=handle_readonly) + readonly=handle_readonly) handle_readonly(self, self.readonly) self._trigger_position_handles = Clock.create_trigger( @@ -1328,6 +1325,7 @@ class TextInput(FocusBehavior, Widget): self._hide_cut_copy_paste(win) self._hide_handles(win) self._win = None + self.focus = value def _ensure_clipboard(self): global Clipboard From e95382d55429ba7ecd229094646c6ffb21dc96f8 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Mon, 2 Jun 2014 18:18:26 -0400 Subject: [PATCH 15/22] Update graphics and ensure win already exists when setting focus directly instead of with touch. --- kivy/uix/textinput.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index a17bcbaf2..36152874e 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -1309,6 +1309,17 @@ class TextInput(FocusBehavior, Widget): _textinput_list.remove(wr) def on_focused(self, instance, value, *largs): + self.focus = value + + win = self._win + if not win: + self._win = win = EventLoop.window + if not win: + Logger.warning('Textinput: unable to get root window') + return + self.cancel_selection() + self._hide_cut_copy_paste(win) + if value: if (not (self.readonly or self.disabled) or _is_desktop and self._keyboard_mode == 'system'): @@ -1317,15 +1328,9 @@ class TextInput(FocusBehavior, Widget): else: self._editable = False else: - win = self._win - if not win: - self._win = win = EventLoop.window - self.cancel_selection() Clock.unschedule(self._do_blink_cursor) - self._hide_cut_copy_paste(win) self._hide_handles(win) self._win = None - self.focus = value def _ensure_clipboard(self): global Clipboard From 5d7e5011d7a9739fb7b45edae5316ac46d7f0868 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sat, 5 Jul 2014 13:43:20 -0400 Subject: [PATCH 16/22] Update focus/unfocus by touch to use on_touch_down and unfocus from window callback instead of binding to each focusable. --- kivy/core/window/__init__.py | 2 ++ kivy/uix/behaviors.py | 33 +++++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py index ba35daad7..ac3c30d0d 100755 --- a/kivy/core/window/__init__.py +++ b/kivy/core/window/__init__.py @@ -24,6 +24,7 @@ from kivy.properties import ListProperty, ObjectProperty, AliasProperty, \ NumericProperty, OptionProperty, StringProperty, BooleanProperty from kivy.utils import platform, reify from kivy.context import get_current_context +from kivy.uix.behaviors import FocusBehavior # late import VKeyboard = None @@ -851,6 +852,7 @@ class WindowBase(EventDispatcher): self.dispatch('on_touch_move', me) elif etype == 'end': self.dispatch('on_touch_up', me) + FocusBehavior._handle_post_on_touch_up(me) def on_touch_down(self, touch): '''Event called when a touch down event is initiated. diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index cbbd8d48d..765554655 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -31,7 +31,7 @@ from kivy.base import EventLoop from kivy.logger import Logger from functools import partial from weakref import ref -from time import clock, time +from time import clock import string # When we are generating documentation, Config doesn't exist @@ -498,6 +498,7 @@ class FocusBehavior(object): _requested_keyboard = False _keyboard = ObjectProperty(None, allownone=True) _keyboards = {} + _processed_touches = [] def _set_keyboard(self, value): focused = self.focused @@ -765,15 +766,31 @@ class FocusBehavior(object): def _keyboard_released(self): self.focused = False - def _focus_on_touch_down(self, instance, touch): - pos = self.to_widget(*touch.pos) + def on_touch_down(self, touch): + if not self.collide_point(*touch.pos): + return + if super(FocusBehavior, self).on_touch_down(touch): + return True if (not self.disabled and self.is_focusable and ('button' not in touch.profile or - not touch.button.startswith('scroll'))): - if self.collide_point(*pos): - self.focused = True - elif self.unfocus_on_touch: - self.focused = False + not touch.button.startswith('scroll')) and + self.collide_point(*touch.pos)): + self.focused = True + FocusBehavior._processed_touches.append(touch) + return True + + @staticmethod + def _handle_post_on_touch_up(touch): + ''' Called by window after each touch has finished. + ''' + touches = FocusBehavior._processed_touches + if touch in touches: + touches.remove(touch) + return + for focusable in FocusBehavior._keyboards.values(): + if focusable is None or not focusable.unfocus_on_touch: + continue + focusable.focused = False def _get_focus_next(self, focus_dir): current = self From 9acb1903bdb3b3fc0bfe12b038805b2b9fdbd0ce Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Fri, 22 Aug 2014 02:46:33 -0400 Subject: [PATCH 17/22] Fix focus behavior to return False when focused with touch. --- kivy/uix/behaviors.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 765554655..12e488c46 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -681,7 +681,8 @@ class FocusBehavior(object): defaults to 'auto'. Can be one of 'auto' or 'managed'. ''' - unfocus_on_touch = BooleanProperty(_keyboard_mode not in ('multi', 'systemandmulti')) + unfocus_on_touch = BooleanProperty(_keyboard_mode not in + ('multi', 'systemandmulti')) '''Whether a instance should lose focus when clicked outside the instance. When a user clicks on a widget that is focus aware and shares the same @@ -773,11 +774,10 @@ class FocusBehavior(object): return True if (not self.disabled and self.is_focusable and ('button' not in touch.profile or - not touch.button.startswith('scroll')) and - self.collide_point(*touch.pos)): + not touch.button.startswith('scroll'))): self.focused = True FocusBehavior._processed_touches.append(touch) - return True + return False @staticmethod def _handle_post_on_touch_up(touch): From 6f69f7d1dab7cee25b11425dba31088800019e09 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sat, 23 Aug 2014 11:26:18 -0400 Subject: [PATCH 18/22] Move input_type to FocusBehavior from TextInput. --- kivy/uix/behaviors.py | 15 ++++++++++++++- kivy/uix/textinput.py | 21 ++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 12e488c46..a48323975 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -681,6 +681,18 @@ class FocusBehavior(object): defaults to 'auto'. Can be one of 'auto' or 'managed'. ''' + input_type = OptionProperty('text', options=('text', 'number', 'url', + 'mail', 'datetime', 'tel', + 'address')) + '''The kind of input keyboard to request. + + .. versionadded:: 1.8.0 + + :attr:`input_type` is an :class:`~kivy.properties.OptionsProperty` and + defaults to 'text'. Can be one of 'text', 'number', 'url', 'mail', + 'datetime', 'tel', 'address'. + ''' + unfocus_on_touch = BooleanProperty(_keyboard_mode not in ('multi', 'systemandmulti')) '''Whether a instance should lose focus when clicked outside the instance. @@ -730,7 +742,8 @@ class FocusBehavior(object): return self._requested_keyboard = True keyboard = self._keyboard =\ - win.request_keyboard(self._keyboard_released, self) + win.request_keyboard(self._keyboard_released, self, + input_type=self.input_type) keyboards = FocusBehavior._keyboards if keyboard not in keyboards: keyboards[keyboard] = None diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 36152874e..64ebfdda0 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -348,9 +348,12 @@ class TextInput(FocusBehavior, Widget): :meth:`~kivy.clock.ClockBase.schedule_once`. .. note:: - `keyboard_mode`, `show_keyboard`, and `hide_keyboard` have been removed - from :class:`TextInput` since they are now inherited from - :class:`~kivy.uix.behaviors.FocusBehavior`. + :attr:`~kivy.uix.behaviors.FocusBehavior.keyboard_mode`, + :meth:`~kivy.uix.behaviors.FocusBehavior.show_keyboard`, + :meth:`~kivy.uix.behaviors.FocusBehavior.hide_keyboard`, + and :attr:`~kivy.uix.behaviors.FocusBehavior.input_type`, + have been removed from :class:`TextInput` since they are now inherited + from :class:`~kivy.uix.behaviors.FocusBehavior`. .. versionchanged:: 1.8.1 :class:`TextInput` now inherits from @@ -2596,18 +2599,6 @@ class TextInput(FocusBehavior, Widget): defaults to 0. ''' - input_type = OptionProperty('text', options=('text', 'number', 'url', - 'mail', 'datetime', 'tel', - 'address')) - '''The kind of input, keyboard to request - - .. versionadded:: 1.8.0 - - :attr:`input_type` is an :class:`~kivy.properties.OptionsProperty` and - defaults to 'text'. Can be one of 'text', 'number', 'url', 'mail', - 'datetime', 'tel', 'address'. - ''' - input_filter = ObjectProperty(None, allownone=True) ''' Filters the input according to the specified mode, if not None. If None, no filtering is applied. From c4a6e4c6830bb007a164d1c0e0d9200c1a37d9a8 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Sat, 23 Aug 2014 11:35:29 -0400 Subject: [PATCH 19/22] Describe mutli user focusing. --- kivy/uix/behaviors.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index a48323975..21746cb3e 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -538,6 +538,13 @@ class FocusBehavior(object): :attr:`keyboard` is a :class:`~kivy.properties.AliasProperty`, defaults to None. + .. note:: + + When Config's `keyboard_mode` is multi, each new touch is considered + a touch by a different user and will focus (if clicked on a + focusable) with a new keyboard. Already focused elements will not lose + their focus (even if clicked on a unfocusable). + .. note: If the keyboard property is set, that keyboard will be used when the From f6018fd30121178e515b75754fbf4f72cba4232d Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Mon, 25 Aug 2014 11:11:17 -0400 Subject: [PATCH 20/22] Rename _processed_touches to ignored_touch and use it in textinput's bubble/selectors. --- kivy/uix/behaviors.py | 22 +++++++++++++++++++--- kivy/uix/textinput.py | 6 ++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 21746cb3e..2b731f89d 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -498,7 +498,23 @@ class FocusBehavior(object): _requested_keyboard = False _keyboard = ObjectProperty(None, allownone=True) _keyboards = {} - _processed_touches = [] + + ignored_touch = [] + '''A list of touches that should not be used to defocus. After on_touch_up, + every touch that is not in :attr:`ignored_touch` will defocus all the + focused widgets, if, the config keyboard mode is not multi. Touches on + focusable widgets that were used to focus are automatically added here. + + Example usage: + + class Unfocusable(Widget): + + def on_touch_down(self, touch): + if self.collide_point(*touch.pos): + FocusBehavior.ignored_touch.append(touch) + + Notice that you need to access this as class, not instance variable. + ''' def _set_keyboard(self, value): focused = self.focused @@ -796,14 +812,14 @@ class FocusBehavior(object): ('button' not in touch.profile or not touch.button.startswith('scroll'))): self.focused = True - FocusBehavior._processed_touches.append(touch) + FocusBehavior.ignored_touch.append(touch) return False @staticmethod def _handle_post_on_touch_up(touch): ''' Called by window after each touch has finished. ''' - touches = FocusBehavior._processed_touches + touches = FocusBehavior.ignored_touch if touch in touches: touches.remove(touch) return diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 64ebfdda0..166a842d9 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -222,6 +222,8 @@ class Selector(ButtonBehavior, Image): touch.push() touch.apply_transform_2d(self.to_widget) self._touch_diff = self.top - touch.y + if self.collide_point(*touch.pos): + FocusBehavior.ignored_touch.append(touch) return super(Selector, self).on_touch_down(touch) finally: touch.pop() @@ -245,6 +247,10 @@ class TextInputCutCopyPaste(Bubble): super(TextInputCutCopyPaste, self).__init__(**kwargs) Clock.schedule_interval(self._check_parent, .5) + def on_touch_down(self, touch): + if self.collide_point(*touch.pos): + FocusBehavior.ignored_touch.append(touch) + def on_textinput(self, instance, value): global Clipboard if value and not Clipboard and not _is_desktop: From 1d1802cff732c0ee21d66a826322e2a793892b44 Mon Sep 17 00:00:00 2001 From: Matthew Einhorn Date: Mon, 25 Aug 2014 11:13:29 -0400 Subject: [PATCH 21/22] Call super in bubble. --- kivy/uix/textinput.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 166a842d9..3f5572982 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -250,6 +250,7 @@ class TextInputCutCopyPaste(Bubble): def on_touch_down(self, touch): if self.collide_point(*touch.pos): FocusBehavior.ignored_touch.append(touch) + return super(TextInputCutCopyPaste, self).on_touch_down(touch) def on_textinput(self, instance, value): global Clipboard From 94a71b4a4a064908daddbc07fdee8e3630719e95 Mon Sep 17 00:00:00 2001 From: akshayaurora Date: Wed, 3 Dec 2014 11:50:32 +0530 Subject: [PATCH 22/22] uix:FocusBehavior ensure touch is passed to children in all cases --- kivy/core/window/window_sdl2.py | 1 + kivy/uix/behaviors.py | 6 ++---- kivy/uix/textinput.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/kivy/core/window/window_sdl2.py b/kivy/core/window/window_sdl2.py index 24981a241..c0c29a80f 100644 --- a/kivy/core/window/window_sdl2.py +++ b/kivy/core/window/window_sdl2.py @@ -344,6 +344,7 @@ class WindowSDL(WindowBase): elif action in ('keydown', 'keyup'): mod, key, scancode, kstr = args + print key if mod in self._meta_keys: try: kstr = unichr(key) diff --git a/kivy/uix/behaviors.py b/kivy/uix/behaviors.py index 2b731f89d..84b50e12e 100644 --- a/kivy/uix/behaviors.py +++ b/kivy/uix/behaviors.py @@ -31,7 +31,7 @@ from kivy.base import EventLoop from kivy.logger import Logger from functools import partial from weakref import ref -from time import clock +from time import clock, time import string # When we are generating documentation, Config doesn't exist @@ -806,14 +806,12 @@ class FocusBehavior(object): def on_touch_down(self, touch): if not self.collide_point(*touch.pos): return - if super(FocusBehavior, self).on_touch_down(touch): - return True if (not self.disabled and self.is_focusable and ('button' not in touch.profile or not touch.button.startswith('scroll'))): self.focused = True FocusBehavior.ignored_touch.append(touch) - return False + return super(FocusBehavior, self).on_touch_down(touch) @staticmethod def _handle_post_on_touch_up(touch): diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index 3f5572982..f6ec218f7 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -222,8 +222,8 @@ class Selector(ButtonBehavior, Image): touch.push() touch.apply_transform_2d(self.to_widget) self._touch_diff = self.top - touch.y - if self.collide_point(*touch.pos): - FocusBehavior.ignored_touch.append(touch) + if self.collide_point(*touch.pos): + FocusBehavior.ignored_touch.append(touch) return super(Selector, self).on_touch_down(touch) finally: touch.pop()