Merge branch 'matham-focus-text4'

This commit is contained in:
akshayaurora 2014-12-03 11:56:19 +05:30
commit 033d0b1f2d
5 changed files with 187 additions and 139 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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):
@ -492,11 +494,28 @@ class FocusBehavior(object):
future version.
'''
_win = None
_focus_win = None
_requested_keyboard = False
_keyboard = ObjectProperty(None, allownone=True)
_keyboards = {}
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
keyboard = self._keyboard
@ -535,6 +554,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
@ -668,15 +694,52 @@ 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'.
'''
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.
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'` or `'systemandmulti'`, 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 = _keyboard_mode
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)
@ -685,23 +748,25 @@ 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:
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')
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
@ -738,12 +803,28 @@ class FocusBehavior(object):
def _keyboard_released(self):
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')):
def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
return
if (not self.disabled and self.is_focusable and
('button' not in touch.profile or
not touch.button.startswith('scroll'))):
self.focused = True
return False
FocusBehavior.ignored_touch.append(touch)
return super(FocusBehavior, self).on_touch_down(touch)
@staticmethod
def _handle_post_on_touch_up(touch):
''' Called by window after each touch has finished.
'''
touches = FocusBehavior.ignored_touch
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
@ -755,7 +836,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
@ -769,7 +850,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
@ -823,6 +904,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

View File

@ -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
@ -221,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()
@ -244,6 +247,11 @@ 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)
return super(TextInputCutCopyPaste, self).on_touch_down(touch)
def on_textinput(self, instance, value):
global Clipboard
if value and not Clipboard and not _is_desktop:
@ -314,7 +322,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 +354,18 @@ class TextInput(Widget):
on the next clock cycle using
:meth:`~kivy.clock.ClockBase.schedule_once`.
.. note::
: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
:class:`~kivy.uix.behaviors.FocusBehavior`.
.. versionchanged:: 1.7.0
`on_double_tap`, `on_triple_tap` and `on_quad_touch` events added.
'''
@ -354,6 +374,7 @@ class TextInput(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]
@ -375,7 +396,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 +422,13 @@ 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 handle_readonly(instance, value):
if value and (not _is_desktop or not self.allow_copy):
self.is_focusable = False
self.bind(padding=self._update_text_options,
tab_width=self._update_text_options,
font_size=self._update_text_options,
@ -409,7 +436,9 @@ 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,
readonly=handle_readonly)
handle_readonly(self, self.readonly)
self._trigger_position_handles = Clock.create_trigger(
self._position_handles)
@ -424,10 +453,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 +986,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 +1318,29 @@ class TextInput(Widget):
if wr in _textinput_list:
_textinput_list.remove(wr)
def _set_window(self, *largs):
def on_focused(self, instance, value, *largs):
self.focus = value
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)
Logger.warning('Textinput: unable to get root window')
return
def on_focus(self, instance, value, *largs):
self._set_window(*largs)
self.cancel_selection()
self._hide_cut_copy_paste(win)
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
Clock.unschedule(self._do_blink_cursor)
self._hide_handles(win)
self._win = None
def _ensure_clipboard(self):
global Clipboard
@ -1414,12 +1387,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 +1929,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 +1940,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 +2031,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 +2042,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 +2467,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):
@ -2629,18 +2606,6 @@ class TextInput(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.
@ -2700,32 +2665,17 @@ class TextInput(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()``.
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.0
.. versionadded:: 1.8.1
:attr:`keyboard_mode` is an :class:`~kivy.properties.OptionsProperty` and
defaults to 'auto'. Can be one of 'auto' or 'managed'.
:attr:`write_tab` is a :class:`~kivy.properties.BooleanProperty` and
defaults to `True`.
'''
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()
if __name__ == '__main__':
from kivy.app import App