Kivy.uix.textinput

一个小小的输入框,纵上下数页文档已不能全不概括,当去源码慢慢寻找,才知道其中作用,才能运用灵活。

Text Input — Kivy 2.3.0 documentation

# -*- encoding: utf-8 -*-
'''
Text Input
==========.. versionadded:: 1.0.4.. image:: images/textinput-mono.jpg
.. image:: images/textinput-multi.jpgThe :class:`TextInput` widget provides a box for editable plain text.Unicode, multiline, cursor navigation, selection and clipboard features
are supported.The :class:`TextInput` uses two different coordinate systems:* (x, y) - coordinates in pixels, mostly used for rendering on screen.
* (col, row) - cursor index in characters / lines, used for selectionand cursor movement.Usage example
-------------To create a multiline :class:`TextInput` (the 'enter' key adds a new line)::from kivy.uix.textinput import TextInputtextinput = TextInput(text='Hello world')To create a singleline :class:`TextInput`, set the :class:`TextInput.multiline`
property to False (the 'enter' key will defocus the TextInput and emit an
:meth:`TextInput.on_text_validate` event)::def on_enter(instance, value):print('User pressed enter in', instance)textinput = TextInput(text='Hello world', multiline=False)textinput.bind(on_text_validate=on_enter)The textinput's text is stored in its :attr:`TextInput.text` property. To run a
callback when the text changes::def on_text(instance, value):print('The widget', instance, 'have:', value)textinput = TextInput()textinput.bind(text=on_text)You can set the :class:`focus <kivy.uix.behaviors.FocusBehavior>` to a
Textinput, meaning that the input box will be highlighted and keyboard focus
will be requested::textinput = TextInput(focus=True)The textinput is defocused if the 'escape' key is pressed, or if another
widget requests the keyboard. You can bind a callback to the focus property to
get notified of focus changes::def on_focus(instance, value):if value:print('User focused', instance)else:print('User defocused', instance)textinput = TextInput()textinput.bind(focus=on_focus)See :class:`~kivy.uix.behaviors.FocusBehavior`, from which the
:class:`TextInput` inherits, for more details.Selection
---------The selection is automatically updated when the cursor position changes.
You can get the currently selected text from the
:attr:`TextInput.selection_text` property.Filtering
---------You can control which text can be added to the :class:`TextInput` by
overwriting :meth:`TextInput.insert_text`. Every string that is typed, pasted
or inserted by any other means into the :class:`TextInput` is passed through
this function. By overwriting it you can reject or change unwanted characters.For example, to write only in capitalized characters::class CapitalInput(TextInput):def insert_text(self, substring, from_undo=False):s = substring.upper()return super(CapitalInput, self).insert_text(s,\from_undo=from_undo)Or to only allow floats (0 - 9 and a single period)::class FloatInput(TextInput):pat = re.compile('[^0-9]')def insert_text(self, substring, from_undo=False):pat = self.patif '.' in self.text:s = re.sub(pat, '', substring)else:s = '.'.join([re.sub(pat, '', s) for s in\substring.split('.', 1)])return super(FloatInput, self).insert_text(s, from_undo=from_undo)Default shortcuts
-----------------=============== ========================================================Shortcuts    Description
--------------- --------------------------------------------------------
Left            Move cursor to left
Right           Move cursor to right
Up              Move cursor to up
Down            Move cursor to down
Home            Move cursor at the beginning of the line
End             Move cursor at the end of the line
PageUp          Move cursor to 3 lines before
PageDown        Move cursor to 3 lines after
Backspace       Delete the selection or character before the cursor
Del             Delete the selection of character after the cursor
Shift + <dir>   Start a text selection. Dir can be Up, Down, Left orRight
Control + c     Copy selection
Control + x     Cut selection
Control + v     Paste clipboard content
Control + a     Select all the content
Control + z     undo
Control + r     redo
=============== ========================================================.. note::To enable Emacs-style keyboard shortcuts, you can use:class:`~kivy.uix.behaviors.emacs.EmacsBehavior`.'''__all__ = ('TextInput', )import re
import sys
from os import environ
from weakref import reffrom kivy.animation import Animation
from kivy.base import EventLoop
from kivy.cache import Cache
from kivy.clock import Clock
from kivy.config import Config
from kivy.core.window import Window
from kivy.metrics import inch
from kivy.utils import boundary, platform
from kivy.uix.behaviors import FocusBehaviorfrom kivy.core.text import Label, DEFAULT_FONT
from kivy.graphics import Color, Rectangle, PushMatrix, PopMatrix, Callback
from kivy.graphics.context_instructions import Transform
from kivy.graphics.texture import Texturefrom kivy.uix.widget import Widget
from kivy.uix.bubble import Bubble
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Imagefrom kivy.properties import StringProperty, NumericProperty, \BooleanProperty, AliasProperty, OptionProperty, \ListProperty, ObjectProperty, VariableListProperty, ColorPropertyCache_register = Cache.register
Cache_append = Cache.append
Cache_get = Cache.get
Cache_remove = Cache.remove
Cache_register('textinput.label', timeout=60.)
Cache_register('textinput.width', timeout=60.)FL_IS_LINEBREAK = 0x01
FL_IS_WORDBREAK = 0x02
FL_IS_NEWLINE = FL_IS_LINEBREAK | FL_IS_WORDBREAK# late binding
Clipboard = None
CutBuffer = None
MarkupLabel = None
_platform = platform# for reloading, we need to keep a list of textinput to retrigger the rendering
_textinput_list = []# cache the result
_is_osx = sys.platform == 'darwin'# When we are generating documentation, Config doesn't exist
_is_desktop = False
if Config:_is_desktop = Config.getboolean('kivy', 'desktop')# register an observer to clear the textinput cache when OpenGL will reload
if 'KIVY_DOC' not in environ:def _textinput_clear_cache(*l):Cache_remove('textinput.label')Cache_remove('textinput.width')for wr in _textinput_list[:]:textinput = wr()if textinput is None:_textinput_list.remove(wr)else:textinput._trigger_refresh_text()textinput._refresh_hint_text()from kivy.graphics.context import get_contextget_context().add_reload_observer(_textinput_clear_cache, True)class Selector(ButtonBehavior, Image):# Internal class for managing the selection Handles.window = ObjectProperty()target = ObjectProperty()matrix = ObjectProperty()def __init__(self, **kwargs):super(Selector, self).__init__(**kwargs)self.matrix = self.target.get_window_matrix()with self.canvas.before:Callback(self.update_transform)PushMatrix()self.transform = Transform()with self.canvas.after:PopMatrix()def update_transform(self, cb):m = self.target.get_window_matrix()if self.matrix != m:self.matrix = mself.transform.identity()self.transform.transform(self.matrix)def transform_touch(self, touch):matrix = self.matrix.inverse()touch.apply_transform_2d(lambda x, y: matrix.transform_point(x, y, 0)[:2])def on_touch_down(self, touch):if self.parent is not EventLoop.window:returntry:touch.push()self.transform_touch(touch)self._touch_diff = self.top - touch.yif self.collide_point(*touch.pos):FocusBehavior.ignored_touch.append(touch)return super(Selector, self).on_touch_down(touch)finally:touch.pop()class TextInputCutCopyPaste(Bubble):# Internal class used for showing the little bubble popup when# copy/cut/paste happen.textinput = ObjectProperty(None)''' Holds a reference to the TextInput this Bubble belongs to.'''but_cut = ObjectProperty(None)but_copy = ObjectProperty(None)but_paste = ObjectProperty(None)but_selectall = ObjectProperty(None)matrix = ObjectProperty(None)_check_parent_ev = Nonedef __init__(self, **kwargs):self.mode = 'normal'super(TextInputCutCopyPaste, self).__init__(**kwargs)self._check_parent_ev = Clock.schedule_interval(self._check_parent, .5)self.matrix = self.textinput.get_window_matrix()with self.canvas.before:Callback(self.update_transform)PushMatrix()self.transform = Transform()with self.canvas.after:PopMatrix()def update_transform(self, cb):m = self.textinput.get_window_matrix()if self.matrix != m:self.matrix = mself.transform.identity()self.transform.transform(self.matrix)def transform_touch(self, touch):matrix = self.matrix.inverse()touch.apply_transform_2d(lambda x, y: matrix.transform_point(x, y, 0)[:2])def on_touch_down(self, touch):try:touch.push()self.transform_touch(touch)if self.collide_point(*touch.pos):FocusBehavior.ignored_touch.append(touch)return super(TextInputCutCopyPaste, self).on_touch_down(touch)finally:touch.pop()def on_touch_up(self, touch):try:touch.push()self.transform_touch(touch)for child in self.content.children:if ref(child) in touch.grab_list:touch.grab_current = childbreakreturn super(TextInputCutCopyPaste, self).on_touch_up(touch)finally:touch.pop()def on_textinput(self, instance, value):global Clipboardif value and not Clipboard and not _is_desktop:value._ensure_clipboard()def _check_parent(self, dt):# this is a prevention to get the Bubble staying on the screen, if the# attached textinput is not on the screen anymore.parent = self.textinputwhile parent is not None:if parent == parent.parent:breakparent = parent.parentif parent is None:self._check_parent_ev.cancel()if self.textinput:self.textinput._hide_cut_copy_paste()def on_parent(self, instance, value):parent = self.textinputmode = self.modeif parent:self.clear_widgets()if mode == 'paste':# show only paste on long touchself.but_selectall.opacity = 1widget_list = [self.but_selectall, ]if not parent.readonly:widget_list.append(self.but_paste)elif parent.readonly:# show only copy for read only text inputwidget_list = (self.but_copy, )else:# normal modewidget_list = (self.but_cut, self.but_copy, self.but_paste)for widget in widget_list:self.add_widget(widget)def do(self, action):textinput = self.textinputif action == 'cut':textinput._cut(textinput.selection_text)elif action == 'copy':textinput.copy()elif action == 'paste':textinput.paste()elif action == 'selectall':textinput.select_all()self.mode = ''anim = Animation(opacity=0, d=.333)anim.bind(on_complete=lambda *args:self.on_parent(self, self.parent))anim.start(self.but_selectall)returnself.hide()def hide(self):parent = self.parentif not parent:returnanim = Animation(opacity=0, d=.225)anim.bind(on_complete=lambda *args: parent.remove_widget(self))anim.start(self)class TextInput(FocusBehavior, Widget):'''TextInput class. See module documentation for more information.:Events:`on_text_validate`Fired only in multiline=False mode when the user hits 'enter'.This will also unfocus the textinput.`on_double_tap`Fired when a double tap happens in the text input. The defaultbehavior selects the text around the cursor position. More info at:meth:`on_double_tap`.`on_triple_tap`Fired when a triple tap happens in the text input. The defaultbehavior selects the line around the cursor position. More info at:meth:`on_triple_tap`.`on_quad_touch`Fired when four fingers are touching the text input. The defaultbehavior selects the whole text. More info at:meth:`on_quad_touch`... warning::When changing a :class:`TextInput` property that requires re-drawing,e.g. modifying the :attr:`text`, the updates occur on the nextclock cycle and not instantly. This might cause any changes to the:class:`TextInput` that occur between the modification and the nextcycle to be ignored, or to use previous values. For example, aftera update to the :attr:`text`, changing the cursor in the same clockframe will move it using the previous text and will likely end up in anincorrect position. The solution is to schedule any updates to occuron the next clock cycle using:meth:`~kivy.clock.ClockBase.schedule_once`... Note::Selection is cancelled when TextInput is focused. If you need toshow selection when TextInput is focused, you should delay(use Clock.schedule) the call to the functions for selectingtext (select_all, select_text)... versionchanged:: 1.10.0`background_disabled_active` has been removed... versionchanged:: 1.9.0:class:`TextInput` now inherits 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`,:meth:`~kivy.uix.behaviors.FocusBehavior.focus`,and :attr:`~kivy.uix.behaviors.FocusBehavior.input_type`have been removed since they are now inheritedfrom :class:`~kivy.uix.behaviors.FocusBehavior`... versionchanged:: 1.7.0`on_double_tap`, `on_triple_tap` and `on_quad_touch` events added.'''__events__ = ('on_text_validate', 'on_double_tap', 'on_triple_tap','on_quad_touch')_resolved_base_dir = Nonedef __init__(self, **kwargs):self._update_graphics_ev = Clock.create_trigger(self._update_graphics, -1)self.is_focusable = kwargs.get('is_focusable', True)self._cursor = [0, 0]self._selection = Falseself._selection_finished = Trueself._selection_touch = Noneself.selection_text = u''self._selection_from = Noneself._selection_to = Noneself._selection_callback = Noneself._handle_left = Noneself._handle_right = Noneself._handle_middle = Noneself._bubble = Noneself._lines_flags = []self._lines_labels = []self._lines_rects = []self._hint_text_flags = []self._hint_text_labels = []self._hint_text_rects = []self._label_cached = Noneself._line_options = Noneself._keyboard_mode = Config.get('kivy', 'keyboard_mode')self._command_mode = Falseself._command = ''self.reset_undo()self._touch_count = 0self._ctrl_l = Falseself._ctrl_r = Falseself._alt_l = Falseself._alt_r = Falseself._refresh_text_from_property_ev = Noneself._long_touch_ev = Noneself._do_blink_cursor_ev = Clock.create_trigger(self._do_blink_cursor, .5, interval=True)self._refresh_line_options_ev = None# [from; to) range of lines being partially or fully rendered# in TextInput's viewportself._visible_lines_range = 0, 0self.interesting_keys = {8: 'backspace',13: 'enter',127: 'del',271: 'enter',273: 'cursor_up',274: 'cursor_down',275: 'cursor_right',276: 'cursor_left',278: 'cursor_home',279: 'cursor_end',280: 'cursor_pgup',281: 'cursor_pgdown',303: 'shift_L',304: 'shift_R',305: 'ctrl_L',306: 'ctrl_R',308: 'alt_L',307: 'alt_R'}super(TextInput, self).__init__(**kwargs)fbind = self.fbindrefresh_line_options = self._trigger_refresh_line_optionsupdate_text_options = self._update_text_optionstrigger_update_graphics = self._trigger_update_graphicsfbind('font_size', refresh_line_options)fbind('font_name', refresh_line_options)fbind('font_context', refresh_line_options)fbind('font_family', refresh_line_options)fbind('base_direction', refresh_line_options)fbind('text_language', refresh_line_options)def handle_readonly(instance, value):if value and (not _is_desktop or not self.allow_copy):self.is_focusable = Falseif (not (value or self.disabled) or _is_desktop andself._keyboard_mode == 'system'):self._editable = Trueelse:self._editable = Falsefbind('padding', update_text_options)fbind('tab_width', update_text_options)fbind('font_size', update_text_options)fbind('font_name', update_text_options)fbind('size', update_text_options)fbind('password', update_text_options)fbind('password_mask', update_text_options)fbind('pos', trigger_update_graphics)fbind('halign', trigger_update_graphics)fbind('readonly', handle_readonly)fbind('focus', self._on_textinput_focused)handle_readonly(self, self.readonly)handles = self._trigger_position_handles = Clock.create_trigger(self._position_handles)self._trigger_show_handles = Clock.create_trigger(self._show_handles, .05)self._trigger_cursor_reset = Clock.create_trigger(self._reset_cursor_blink)self._trigger_update_cutbuffer = Clock.create_trigger(self._update_cutbuffer)refresh_line_options()self._trigger_refresh_text()fbind('pos', handles)fbind('size', handles)# when the gl context is reloaded, trigger the text rendering again._textinput_list.append(ref(self, TextInput._reload_remove_observer))if platform == 'linux':self._ensure_clipboard()def on_text_validate(self):passdef cursor_index(self, cursor=None):'''Return the cursor index in the text/value.'''if not cursor:cursor = self.cursortry:l = self._linesif len(l) == 0:return 0lf = self._lines_flagsindex, cr = cursorfor row in range(cr):if row >= len(l):continueindex += len(l[row])if lf[row] & FL_IS_LINEBREAK:index += 1if lf[cr] & FL_IS_LINEBREAK:index += 1return indexexcept IndexError:return 0def cursor_offset(self):'''Get the cursor x offset on the current line.'''offset = 0row = int(self.cursor_row)col = int(self.cursor_col)_lines = self._linesif col and row < len(_lines):offset = self._get_text_width(_lines[row][:col],self.tab_width,self._label_cached)return offsetdef get_cursor_from_index(self, index):'''Return the (col, row) of the cursor from text index.'''index = boundary(index, 0, len(self.text))if index <= 0:return 0, 0lf = self._lines_flagsl = self._linesi = 0for row in range(len(l)):ni = i + len(l[row])if lf[row] & FL_IS_LINEBREAK:ni += 1i += 1if ni >= index:return index - i, rowi = nireturn index, rowdef select_text(self, start, end):''' Select a portion of text displayed in this TextInput... versionadded:: 1.4.0:Parameters:`start`Index of textinput.text from where to start selection`end`Index of textinput.text till which the selection should bedisplayed'''if end < start:raise Exception('end must be superior to start')m = len(self.text)self._selection_from = boundary(start, 0, m)self._selection_to = boundary(end, 0, m)self._selection_finished = Trueself._update_selection(True)self._update_graphics_selection()def select_all(self):''' Select all of the text displayed in this TextInput... versionadded:: 1.4.0'''self.select_text(0, len(self.text))re_indent = re.compile(r'^(\s*|)')def _auto_indent(self, substring):index = self.cursor_index()if index > 0:_text = self.textline_start = _text.rfind('\n', 0, index)if line_start > -1:line = _text[line_start + 1:index]indent = self.re_indent.match(line).group()substring += indentreturn substringdef insert_text(self, substring, from_undo=False):'''Insert new text at the current cursor position. Override thisfunction in order to pre-process text for input validation.'''if self.readonly or not substring or not self._lines:returnif isinstance(substring, bytes):substring = substring.decode('utf8')if self.replace_crlf:substring = substring.replace(u'\r\n', u'\n')self._hide_handles(EventLoop.window)if not from_undo and self.multiline and self.auto_indent \and substring == u'\n':substring = self._auto_indent(substring)mode = self.input_filterif mode not in (None, 'int', 'float'):substring = mode(substring, from_undo)if not substring:returncc, cr = self.cursorsci = self.cursor_indexci = sci()text = self._lines[cr]len_str = len(substring)new_text = text[:cc] + substring + text[cc:]if mode is not None:if mode == 'int':if not re.match(self._insert_int_pat, new_text):returnelif mode == 'float':if not re.match(self._insert_float_pat, new_text):returnself._set_line_text(cr, new_text)wrap = (self._get_text_width(new_text,self.tab_width,self._label_cached) > (self.width - self.padding[0] -self.padding[2]))if len_str > 1 or substring == u'\n' or wrap:# Avoid refreshing text on every keystroke.# Allows for faster typing of text when the amount of text in# TextInput gets large.start, finish, lines,\lineflags, len_lines = self._get_line_from_cursor(cr, new_text)# calling trigger here could lead to wrong cursor positioning# and repeating of text when keys are added rapidly in a automated# fashion. From Android Keyboard for example.self._refresh_text_from_property('insert', start, finish, lines,lineflags, len_lines)self.cursor = self.get_cursor_from_index(ci + len_str)# handle undo and redoself._set_unredo_insert(ci, ci + len_str, substring, from_undo)def _get_line_from_cursor(self, start, new_text):# get current paragraph from cursor positionfinish = startlines = self._lineslinesflags = self._lines_flagsif start and not linesflags[start]:start -= 1new_text = u''.join((lines[start], new_text))try:while not linesflags[finish + 1]:new_text = u''.join((new_text, lines[finish + 1]))finish += 1except IndexError:passlines, lineflags = self._split_smart(new_text)len_lines = max(1, len(lines))return start, finish, lines, lineflags, len_linesdef _set_unredo_insert(self, ci, sci, substring, from_undo):# handle undo and redoif from_undo:returnself._undo.append({'undo_command': ('insert', ci, sci),'redo_command': (ci, substring)})# reset redo when undo is appended toself._redo = []def reset_undo(self):'''Reset undo and redo lists from memory... versionadded:: 1.3.0'''self._redo = self._undo = []def do_redo(self):'''Do redo operation... versionadded:: 1.3.0This action re-does any command that has been un-done bydo_undo/ctrl+z. This function is automatically called when`ctrl+r` keys are pressed.'''try:x_item = self._redo.pop()undo_type = x_item['undo_command'][0]_get_cusror_from_index = self.get_cursor_from_indexif undo_type == 'insert':ci, substring = x_item['redo_command']self.cursor = _get_cusror_from_index(ci)self.insert_text(substring, True)elif undo_type == 'bkspc':self.cursor = _get_cusror_from_index(x_item['redo_command'])self.do_backspace(from_undo=True)elif undo_type == 'shiftln':direction, rows, cursor = x_item['redo_command'][1:]self._shift_lines(direction, rows, cursor, True)else:# delselci, sci = x_item['redo_command']self._selection_from = ciself._selection_to = sciself._selection = Trueself.delete_selection(True)self.cursor = _get_cusror_from_index(ci)self._undo.append(x_item)except IndexError:# reached at top of undo listpassdef do_undo(self):'''Do undo operation... versionadded:: 1.3.0This action un-does any edits that have been made since the lastcall to reset_undo().This function is automatically called when `ctrl+z` keys are pressed.'''try:x_item = self._undo.pop()undo_type = x_item['undo_command'][0]self.cursor = self.get_cursor_from_index(x_item['undo_command'][1])if undo_type == 'insert':ci, sci = x_item['undo_command'][1:]self._selection_from = ciself._selection_to = sciself._selection = Trueself.delete_selection(True)elif undo_type == 'bkspc':substring = x_item['undo_command'][2:][0]self.insert_text(substring, True)elif undo_type == 'shiftln':direction, rows, cursor = x_item['undo_command'][1:]self._shift_lines(direction, rows, cursor, True)else:# delselsubstring = x_item['undo_command'][2:][0]self.insert_text(substring, True)self._redo.append(x_item)except IndexError:# reached at top of undo listpassdef do_backspace(self, from_undo=False, mode='bkspc'):'''Do backspace operation from the current cursor position.This action might do several things:- removing the current selection if available.- removing the previous char and move the cursor back.- do nothing, if we are at the start.'''# IME system handles its own backspacesif self.readonly or self._ime_composition:returncc, cr = self.cursor_lines = self._linestext = _lines[cr]cursor_index = self.cursor_index()text_last_line = _lines[cr - 1]if cc == 0 and cr == 0:return_lines_flags = self._lines_flagsstart = crif cc == 0:substring = u'\n' if _lines_flags[cr] else u' 'new_text = text_last_line + textself._set_line_text(cr - 1, new_text)self._delete_line(cr)start = cr - 1else:# ch = text[cc-1]substring = text[cc - 1]new_text = text[:cc - 1] + text[cc:]self._set_line_text(cr, new_text)# refresh just the current line instead of the whole textstart, finish, lines, lineflags, len_lines =\self._get_line_from_cursor(start, new_text)# avoid trigger refresh, leads to issue with# keys/text send rapidly through code.self._refresh_text_from_property('del', start, finish, lines,lineflags, len_lines)self.cursor = self.get_cursor_from_index(cursor_index - 1)# handle undo and redoself._set_undo_redo_bkspc(cursor_index,cursor_index - 1,substring, from_undo)def _set_undo_redo_bkspc(self, ol_index, new_index, substring, from_undo):# handle undo and redo for backspaceif from_undo:returnself._undo.append({'undo_command': ('bkspc', new_index, substring),'redo_command': ol_index})# reset redo when undo is appended toself._redo = []_re_whitespace = re.compile(r'\s+')def _move_cursor_word_left(self, index=None):pos = index or self.cursor_index()if pos == 0:return self.cursorlines = self._linescol, row = self.get_cursor_from_index(pos)if col == 0:row -= 1col = len(lines[row])while True:matches = list(self._re_whitespace.finditer(lines[row], 0, col))if not matches:if col == 0:if row == 0:return 0, 0row -= 1col = len(lines[row])continuereturn 0, rowmatch = matches[-1]mpos = match.end()if mpos == col:if len(matches) > 1:match = matches[-2]mpos = match.end()else:if match.start() == 0:if row == 0:return 0, 0row -= 1col = len(lines[row])continuereturn 0, rowcol = mposreturn col, rowdef _move_cursor_word_right(self, index=None):pos = index or self.cursor_index()col, row = self.get_cursor_from_index(pos)lines = self._linesmrow = len(lines) - 1if row == mrow and col == len(lines[row]):return col, rowif col == len(lines[row]):row += 1col = 0while True:matches = list(self._re_whitespace.finditer(lines[row], col))if not matches:if col == len(lines[row]):if row == mrow:return col, rowrow += 1col = 0continuereturn len(lines[row]), rowmatch = matches[0]mpos = match.start()if mpos == col:if len(matches) > 1:match = matches[1]mpos = match.start()else:if match.end() == len(lines[row]):if row == mrow:return col, rowrow += 1col = 0continuereturn len(lines[row]), rowcol = mposreturn col, rowdef _expand_range(self, ifrom, ito=None):if ito is None:ito = ifromrfrom = self.get_cursor_from_index(ifrom)[1]rtcol, rto = self.get_cursor_from_index(ito)rfrom, rto = self._expand_rows(rfrom, rto + 1 if rtcol else rto)return (self.cursor_index((0, rfrom)),self.cursor_index((0, rto)))def _expand_rows(self, rfrom, rto=None):if rto is None or rto == rfrom:rto = rfrom + 1lines = self._linesflags = list(reversed(self._lines_flags))while rfrom > 0 and not (flags[rfrom - 1] & FL_IS_NEWLINE):rfrom -= 1rmax = len(lines) - 1while 0 < rto < rmax and not (flags[rto - 1] & FL_IS_NEWLINE):rto += 1return max(0, rfrom), min(rmax, rto)def _shift_lines(self, direction, rows=None, old_cursor=None,from_undo=False):if self._selection_callback:if from_undo:self._selection_callback.cancel()else:returnlines = self._linesflags = list(reversed(self._lines_flags))labels = self._lines_labelsrects = self._lines_rectsorig_cursor = self.cursorsel = Noneif old_cursor is not None:self.cursor = old_cursorif not rows:sindex = self.selection_fromeindex = self.selection_toif (sindex or eindex) and sindex != eindex:sindex, eindex = tuple(sorted((sindex, eindex)))sindex, eindex = self._expand_range(sindex, eindex)else:sindex, eindex = self._expand_range(self.cursor_index())srow = self.get_cursor_from_index(sindex)[1]erow = self.get_cursor_from_index(eindex)[1]sel = sindex, eindexif direction < 0 and srow > 0:psrow, perow = self._expand_rows(srow - 1)rows = ((srow, erow), (psrow, perow))elif direction > 0 and erow < len(lines) - 1:psrow, perow = self._expand_rows(erow)rows = ((srow, erow), (psrow, perow))if rows:(srow, erow), (psrow, perow) = rowsif direction < 0:m1srow, m1erow = psrow, perowm2srow, m2erow = srow, erowcdiff = psrow - perowxdiff = srow - erowelse:m1srow, m1erow = srow, erowm2srow, m2erow = psrow, perowcdiff = perow - psrowxdiff = erow - srowself._lines_flags = list(reversed(flags[:m1srow] + flags[m2srow:m2erow] + flags[m1srow:m1erow] +flags[m2erow:]))self._lines[:] = (lines[:m1srow] + lines[m2srow:m2erow] +lines[m1srow:m1erow] + lines[m2erow:])self._lines_labels = (labels[:m1srow] + labels[m2srow:m2erow] +labels[m1srow:m1erow] + labels[m2erow:])self._lines_rects = (rects[:m1srow] + rects[m2srow:m2erow] +rects[m1srow:m1erow] + rects[m2erow:])self._trigger_update_graphics()csrow = srow + cdiffcerow = erow + cdiffsel = (self.cursor_index((0, csrow)),self.cursor_index((0, cerow)))self.cursor = self.cursor_col, self.cursor_row + cdiffif not from_undo:undo_rows = ((srow + cdiff, erow + cdiff),(psrow - xdiff, perow - xdiff))self._undo.append({'undo_command': ('shiftln', direction * -1, undo_rows,self.cursor),'redo_command': ('shiftln', direction, rows, orig_cursor),})self._redo = []if sel:def cb(dt):self.select_text(*sel)self._selection_callback = Noneself._selection_callback = Clock.schedule_once(cb)def do_cursor_movement(self, action, control=False, alt=False):'''Move the cursor relative to its current position.Action can be one of :- cursor_left: move the cursor to the left- cursor_right: move the cursor to the right- cursor_up: move the cursor on the previous line- cursor_down: move the cursor on the next line- cursor_home: move the cursor at the start of the current line- cursor_end: move the cursor at the end of current line- cursor_pgup: move one "page" before- cursor_pgdown: move one "page" afterIn addition, the behavior of certain actions can be modified:- control + cursor_left: move the cursor one word to the left- control + cursor_right: move the cursor one word to the right- control + cursor_up: scroll up one line- control + cursor_down: scroll down one line- control + cursor_home: go to beginning of text- control + cursor_end: go to end of text- alt + cursor_up: shift line(s) up- alt + cursor_down: shift line(s) down.. versionchanged:: 1.9.1'''if not self._lines:returnpgmove_speed = int(self.height /(self.line_height + self.line_spacing) - 1)col, row = self.cursorif action == 'cursor_up':if self.multiline and control:self.scroll_y = max(0, self.scroll_y - self.line_height)elif not self.readonly and self.multiline and alt:self._shift_lines(-1)returnelse:row = max(row - 1, 0)col = min(len(self._lines[row]), col)elif action == 'cursor_down':if self.multiline and control:maxy = self.minimum_height - self.heightself.scroll_y = max(0, min(maxy,self.scroll_y + self.line_height))elif not self.readonly and self.multiline and alt:self._shift_lines(1)returnelse:row = min(row + 1, len(self._lines) - 1)col = min(len(self._lines[row]), col)elif action == 'cursor_home':col = 0if control:row = 0elif action == 'cursor_end':if control:row = len(self._lines) - 1col = len(self._lines[row])elif action == 'cursor_pgup':row = max(0, row - pgmove_speed)col = min(len(self._lines[row]), col)elif action == 'cursor_pgdown':row = min(row + pgmove_speed, len(self._lines) - 1)col = min(len(self._lines[row]), col)elif (self._selection and self._selection_finished andself._selection_from < self._selection_to andaction == 'cursor_left'):current_selection_to = self._selection_towhile self._selection_from != current_selection_to:current_selection_to -= 1if col:col -= 1else:row -= 1col = len(self._lines[row])elif (self._selection and self._selection_finished andself._selection_from > self._selection_to andaction == 'cursor_right'):current_selection_to = self._selection_towhile self._selection_from != current_selection_to:current_selection_to += 1if len(self._lines[row]) > col:col += 1else:row += 1col = 0elif action == 'cursor_left':if not self.password and control:col, row = self._move_cursor_word_left()else:if col == 0:if row:row -= 1col = len(self._lines[row])else:col, row = col - 1, rowelif action == 'cursor_right':if not self.password and control:col, row = self._move_cursor_word_right()else:if col == len(self._lines[row]):if row < len(self._lines) - 1:col = 0row += 1else:col, row = col + 1, rowdont_move_cursor = control and action in ['cursor_up', 'cursor_down']if dont_move_cursor:self._trigger_update_graphics()else:self.cursor = (col, row)def get_cursor_from_xy(self, x, y):'''Return the (col, row) of the cursor from an (x, y) position.'''padding_left = self.padding[0]padding_top = self.padding[1]l = self._linesdy = self.line_height + self.line_spacingcx = x - self.xscrl_y = self.scroll_yscrl_x = self.scroll_xscrl_y = scrl_y / dy if scrl_y > 0 else 0cy = (self.top - padding_top + scrl_y * dy) - ycy = int(boundary(round(cy / dy - 0.5), 0, len(l) - 1))_get_text_width = self._get_text_width_tab_width = self.tab_width_label_cached = self._label_cached# Offset for horizontal text alignmentxoff = 0halign = self.halignbase_dir = self.base_direction or self._resolved_base_dirauto_halign_r = halign == 'auto' and base_dir and 'rtl' in base_dirif halign == 'center':viewport_width = self.width - padding_left - self.padding[2]  # _rxoff = int((viewport_width - self._get_row_width(cy)) / 2)elif halign == 'right' or auto_halign_r:viewport_width = self.width - padding_left - self.padding[2]  # _rxoff = viewport_width - self._get_row_width(cy)for i in range(0, len(l[cy])):if xoff + _get_text_width(l[cy][:i], _tab_width, _label_cached) + \_get_text_width(l[cy][i], _tab_width, _label_cached) * 0.6 +\padding_left > cx + scrl_x:cx = ibreakreturn cx, cy## Selection control#def cancel_selection(self):'''Cancel current selection (if any).'''self._selection_from = self._selection_to = self.cursor_index()self._selection = Falseself._selection_finished = Trueself._selection_touch = Noneself.selection_text = u''self._trigger_update_graphics()def delete_selection(self, from_undo=False):'''Delete the current text selection (if any).'''if self.readonly:returnself._hide_handles(EventLoop.window)scrl_x = self.scroll_xscrl_y = self.scroll_ycc, cr = self.cursorif not self._selection:returntext = self.texta, b = self._selection_from, self._selection_toif a > b:a, b = b, aself.cursor = cursor = self.get_cursor_from_index(a)start = cursorfinish = self.get_cursor_from_index(b)cur_line = self._lines[start[1]][:start[0]] +\self._lines[finish[1]][finish[0]:]lines, lineflags = self._split_smart(cur_line)len_lines = len(lines)if start[1] == finish[1]:self._set_line_text(start[1], cur_line)else:self._refresh_text_from_property('del', start[1], finish[1], lines,lineflags, len_lines)self.scroll_x = scrl_xself.scroll_y = scrl_y# handle undo and redo for delete selectionself._set_unredo_delsel(a, b, text[a:b], from_undo)self.cancel_selection()def _set_unredo_delsel(self, a, b, substring, from_undo):# handle undo and redo for backspaceif from_undo:returnself._undo.append({'undo_command': ('delsel', a, substring),'redo_command': (a, b)})# reset redo when undo is appended toself._redo = []def _update_selection(self, finished=False):'''Update selection text and order of from/to if finished is True.Can be called multiple times until finished is True.'''a, b = int(self._selection_from), int(self._selection_to)if a > b:a, b = b, aself._selection_finished = finished_selection_text = self.text[a:b]self.selection_text = ("" if not self.allow_copy else((self.password_mask * (b - a)) ifself.password else _selection_text))if not finished:self._selection = Trueelse:self._selection = bool(len(_selection_text))self._selection_touch = Noneif a == 0:# update graphics only on new line# allows smoother scrolling, noticeably# faster when dealing with large text.self._update_graphics_selection()# self._trigger_update_graphics()## Touch control#def long_touch(self, dt):self._long_touch_ev = Noneif self._selection_to == self._selection_from:pos = self.to_local(*self._long_touch_pos, relative=False)self._show_cut_copy_paste(pos, EventLoop.window, mode='paste')def on_double_tap(self):'''This event is dispatched when a double tap happensinside TextInput. The default behavior is to select theword around the current cursor position. Override this to providedifferent behavior. Alternatively, you can bind to thisevent to provide additional functionality.'''ci = int(self.cursor_index())cc = int(self.cursor_col)line = self._lines[self.cursor_row]len_line = len(line)start = max(0, len(line[:cc]) - line[:cc].rfind(u' ') - 1)end = line[cc:].find(u' ')end = end if end > - 1 else (len_line - cc)Clock.schedule_once(lambda dt: self.select_text(ci - start, ci + end))def on_triple_tap(self):'''This event is dispatched when a triple tap happensinside TextInput. The default behavior is to select theline around current cursor position. Override this to providedifferent behavior. Alternatively, you can bind to thisevent to provide additional functionality.'''ci = self.cursor_index()sindex, eindex = self._expand_range(ci)Clock.schedule_once(lambda dt: self.select_text(sindex, eindex))def on_quad_touch(self):'''This event is dispatched when four fingers are touchinginside TextInput. The default behavior is to select all text.Override this to provide different behavior. Alternatively,you can bind to this event to provide additional functionality.'''Clock.schedule_once(lambda dt: self.select_all())def on_touch_down(self, touch):if self.disabled:returntouch_pos = touch.posif not self.collide_point(*touch_pos):return Falseif super(TextInput, self).on_touch_down(touch):return Trueif self.focus:self._trigger_cursor_reset()# Check for scroll wheelif 'button' in touch.profile and touch.button.startswith('scroll'):# TODO: implement 'scrollleft' and 'scrollright'scroll_type = touch.button[6:]if scroll_type == 'down':if self.multiline:if self.scroll_y > 0:self.scroll_y -= self.line_heightself._trigger_update_graphics()else:if self.scroll_x > 0:self.scroll_x -= self.line_heightself._trigger_update_graphics()if scroll_type == 'up':if self.multiline:viewport_height = self.height\- self.padding[1] - self.padding[3]text_height = len(self._lines) * (self.line_height+ self.line_spacing)if viewport_height < text_height - self.scroll_y:self.scroll_y += self.line_heightself._trigger_update_graphics()else:if (self.scroll_x + self.width <self._lines_rects[-1].texture.size[0]):self.scroll_x += self.line_heightself._trigger_update_graphics()return Truetouch.grab(self)self._touch_count += 1if touch.is_double_tap:self.dispatch('on_double_tap')if touch.is_triple_tap:self.dispatch('on_triple_tap')if self._touch_count == 4:self.dispatch('on_quad_touch')self._hide_cut_copy_paste(EventLoop.window)# schedule long touch for pasteself._long_touch_pos = touch.posself._long_touch_ev = Clock.schedule_once(self.long_touch, .5)self.cursor = self.get_cursor_from_xy(*touch_pos)if not self._selection_touch:self.cancel_selection()self._selection_touch = touchself._selection_from = self._selection_to = self.cursor_index()self._update_selection()if CutBuffer and 'button' in touch.profile and \touch.button == 'middle':self.insert_text(CutBuffer.get_cutbuffer())return Truereturn Truedef on_touch_move(self, touch):if touch.grab_current is not self:returnif not self.focus:touch.ungrab(self)if self._selection_touch is touch:self._selection_touch = Nonereturn Falseif self._selection_touch is touch:self.cursor = self.get_cursor_from_xy(touch.x, touch.y)self._selection_to = self.cursor_index()self._update_selection()return Truedef on_touch_up(self, touch):if touch.grab_current is not self:returntouch.ungrab(self)self._touch_count -= 1# schedule long touch for pasteif self._long_touch_ev is not None:self._long_touch_ev.cancel()self._long_touch_ev = Noneif not self.focus:return Falseif self._selection_touch is touch:self._selection_to = self.cursor_index()self._update_selection(True)# show Bubblewin = EventLoop.windowif self._selection_to != self._selection_from:self._show_cut_copy_paste(touch.pos, win)elif self.use_handles:self._hide_handles()handle_middle = self._handle_middleif handle_middle is None:self._handle_middle = handle_middle = Selector(source=self.handle_image_middle,window=win,target=self,size_hint=(None, None),size=('45dp', '45dp'))handle_middle.bind(on_press=self._handle_pressed,on_touch_move=self._handle_move,on_release=self._handle_released)if not self._handle_middle.parent and self.text:EventLoop.window.add_widget(handle_middle, canvas='after')self._position_handles(mode='middle')return Truedef _handle_pressed(self, instance):self._hide_cut_copy_paste()sf, st = self._selection_from, self.selection_toif sf > st:self._selection_from, self._selection_to = st, sfdef _handle_released(self, instance):sf, st = self._selection_from, self.selection_toif sf == st:returnself._update_selection()self._show_cut_copy_paste((instance.right if instance is self._handle_left else instance.x,instance.top + self.line_height),EventLoop.window)def _handle_move(self, instance, touch):if touch.grab_current != instance:returnget_cursor = self.get_cursor_from_xyhandle_right = self._handle_righthandle_left = self._handle_lefthandle_middle = self._handle_middletry:touch.push()touch.apply_transform_2d(self.to_widget)x, y = touch.posfinally:touch.pop()cursor = get_cursor(x,y + instance._touch_diff + (self.line_height / 2))if instance != touch.grab_current:returnif instance == handle_middle:self.cursor = cursorself._position_handles(mode='middle')returnci = self.cursor_index(cursor=cursor)sf, st = self._selection_from, self.selection_toif instance == handle_left:self._selection_from = cielif instance == handle_right:self._selection_to = ciself._trigger_update_graphics()self._trigger_position_handles()def _position_handles(self, *args, **kwargs):if not self.text:returnmode = kwargs.get('mode', 'both')lh = self.line_heighthandle_middle = self._handle_middleif handle_middle:hp_mid = self.cursor_pospos = self.to_local(*hp_mid, relative=True)handle_middle.x = pos[0] - handle_middle.width / 2handle_middle.top = pos[1] - lhif mode[0] == 'm':returngroup = self.canvas.get_group('selection')if not group:returnEventLoop.window.remove_widget(self._handle_middle)handle_left = self._handle_leftif not handle_left:returnhp_left = group[2].poshandle_left.pos = self.to_local(*hp_left, relative=True)handle_left.x -= handle_left.widthhandle_left.y -= handle_left.heighthandle_right = self._handle_rightlast_rect = group[-1]hp_right = last_rect.pos[0], last_rect.pos[1]x, y = self.to_local(*hp_right, relative=True)handle_right.x = x + last_rect.size[0]handle_right.y = y - handle_right.heightdef _hide_handles(self, win=None):win = win or EventLoop.windowif win is None:returnwin.remove_widget(self._handle_right)win.remove_widget(self._handle_left)win.remove_widget(self._handle_middle)def _show_handles(self, dt):if not self.use_handles or not self.text:returnwin = EventLoop.windowhandle_right = self._handle_righthandle_left = self._handle_leftif self._handle_left is None:self._handle_left = handle_left = Selector(source=self.handle_image_left,target=self,window=win,size_hint=(None, None),size=('45dp', '45dp'))handle_left.bind(on_press=self._handle_pressed,on_touch_move=self._handle_move,on_release=self._handle_released)self._handle_right = handle_right = Selector(source=self.handle_image_right,target=self,window=win,size_hint=(None, None),size=('45dp', '45dp'))handle_right.bind(on_press=self._handle_pressed,on_touch_move=self._handle_move,on_release=self._handle_released)else:if self._handle_left.parent:self._position_handles()returnif not self.parent:returnself._trigger_position_handles()if self.selection_from != self.selection_to:self._handle_left.opacity = self._handle_right.opacity = 0win.add_widget(self._handle_left, canvas='after')win.add_widget(self._handle_right, canvas='after')anim = Animation(opacity=1, d=.4)anim.start(self._handle_right)anim.start(self._handle_left)def _show_cut_copy_paste(self, pos, win, parent_changed=False,mode='', pos_in_window=False, *l):# Show a bubble with cut copy and paste buttonsif not self.use_bubble:returnbubble = self._bubbleif bubble is None:self._bubble = bubble = TextInputCutCopyPaste(textinput=self)self.fbind('parent', self._show_cut_copy_paste, pos, win, True)self.bind(focus=lambda *args: self._hide_cut_copy_paste(win))self.bind(cursor_pos=lambda *args: self._hide_cut_copy_paste(win))else:win.remove_widget(bubble)if not self.parent:returnif parent_changed:return# Search the position from the touch to the windowlh, ls = self.line_height, self.line_spacingx, y = post_pos = (x, y) if pos_in_window else self.to_window(x, y)bubble_size = bubble.sizebubble_hw = bubble_size[0] / 2.win_size = win.sizebubble_pos = (t_pos[0], t_pos[1] + inch(.25))if (bubble_pos[0] - bubble_hw) < 0:# bubble beyond left of windowif bubble_pos[1] > (win_size[1] - bubble_size[1]):# bubble above window heightbubble_pos = (bubble_hw, (t_pos[1]) - (lh + ls + inch(.25)))bubble.arrow_pos = 'top_left'else:bubble_pos = (bubble_hw, bubble_pos[1])bubble.arrow_pos = 'bottom_left'elif (bubble_pos[0] + bubble_hw) > win_size[0]:# bubble beyond right of windowif bubble_pos[1] > (win_size[1] - bubble_size[1]):# bubble above window heightbubble_pos = (win_size[0] - bubble_hw,(t_pos[1]) - (lh + ls + inch(.25)))bubble.arrow_pos = 'top_right'else:bubble_pos = (win_size[0] - bubble_hw, bubble_pos[1])bubble.arrow_pos = 'bottom_right'else:if bubble_pos[1] > (win_size[1] - bubble_size[1]):# bubble above window heightbubble_pos = (bubble_pos[0],(t_pos[1]) - (lh + ls + inch(.25)))bubble.arrow_pos = 'top_mid'else:bubble.arrow_pos = 'bottom_mid'bubble_pos = self.to_widget(*bubble_pos, relative=True)bubble.center_x = bubble_pos[0]if bubble.arrow_pos[0] == 't':bubble.top = bubble_pos[1]else:bubble.y = bubble_pos[1]bubble.mode = modeAnimation.cancel_all(bubble)bubble.opacity = 0win.add_widget(bubble, canvas='after')Animation(opacity=1, d=.225).start(bubble)def _hide_cut_copy_paste(self, win=None):bubble = self._bubbleif not bubble:returnbubble.hide()## Private#@staticmethoddef _reload_remove_observer(wr):# called when the textinput is deletedif wr in _textinput_list:_textinput_list.remove(wr)def _on_textinput_focused(self, instance, value, *largs):win = EventLoop.windowself.cancel_selection()self._hide_cut_copy_paste(win)if value:if (not (self.readonly or self.disabled) or _is_desktop andself._keyboard_mode == 'system'):self._trigger_cursor_reset()self._editable = Trueelse:self._editable = Falseelse:self._do_blink_cursor_ev.cancel()self._hide_handles(win)def _ensure_clipboard(self):global Clipboard, CutBufferif not Clipboard:from kivy.core.clipboard import Clipboard, CutBufferdef cut(self):''' Copy current selection to clipboard then delete it from TextInput... versionadded:: 1.8.0'''self._cut(self.selection_text)def _cut(self, data):self._ensure_clipboard()Clipboard.copy(data)self.delete_selection()def copy(self, data=''):''' Copy the value provided in argument `data` into current clipboard.If data is not of type string it will be converted to string.If no data is provided then current selection if present is copied... versionadded:: 1.8.0'''self._ensure_clipboard()if data:return Clipboard.copy(data)if self.selection_text:return Clipboard.copy(self.selection_text)def paste(self):''' Insert text from system :class:`~kivy.core.clipboard.Clipboard`into the :class:`~kivy.uix.textinput.TextInput` at current cursorposition... versionadded:: 1.8.0'''self._ensure_clipboard()data = Clipboard.paste()self.delete_selection()self.insert_text(data)def _update_cutbuffer(self, *args):CutBuffer.set_cutbuffer(self.selection_text)def _get_text_width(self, text, tab_width, _label_cached):# Return the width of a text, according to the current line optionskw = self._get_line_options()try:cid = u'{}\0{}\0{}'.format(text, self.password, kw)except UnicodeDecodeError:cid = '{}\0{}\0{}'.format(text, self.password, kw)width = Cache_get('textinput.width', cid)if width:return widthif not _label_cached:_label_cached = self._label_cachedtext = text.replace('\t', ' ' * tab_width)if not self.password:width = _label_cached.get_extents(text)[0]else:width = _label_cached.get_extents(self.password_mask * len(text))[0]Cache_append('textinput.width', cid, width)return widthdef on_cursor_blink(self, instance, value):# trigger blink event reset to switch blinking while focusedself._reset_cursor_blink()def _do_blink_cursor(self, dt):if not self.cursor_blink:# ignore event if not triggered,# stop if cursor_blink value changed right nowif self._do_blink_cursor_ev.is_triggered:self._do_blink_cursor_ev.cancel()# don't blink, make cursor visibleself._cursor_blink = Falsereturn# Callback for blinking the cursor.self._cursor_blink = not self._cursor_blinkdef _reset_cursor_blink(self, *args):self._do_blink_cursor_ev.cancel()self._cursor_blink = Falseself._do_blink_cursor_ev()def on_cursor(self, instance, value):# When the cursor is moved, reset cursor blinking to keep it showing,# and update all the graphics.if self.focus:self._trigger_cursor_reset()self._trigger_update_graphics()def _delete_line(self, idx):# Delete current line, and fix cursor positionassert(idx < len(self._lines))self._lines_flags.pop(idx)self._lines_labels.pop(idx)self._lines.pop(idx)self.cursor = self.cursordef _set_line_text(self, line_num, text):# Set current line with other text than the default one.self._lines_labels[line_num] = self._create_line_label(text)self._lines[line_num] = textdef _trigger_refresh_line_options(self, *largs):if self._refresh_line_options_ev is not None:self._refresh_line_options_ev.cancel()else:self._refresh_line_options_ev = Clock.create_trigger(self._refresh_line_options, 0)self._refresh_line_options_ev()def _refresh_line_options(self, *largs):self._line_options = Noneself._get_line_options()self._refresh_text_from_property()self._refresh_hint_text()self.cursor = self.get_cursor_from_index(len(self.text))def _trigger_refresh_text(self, *largs):if len(largs) and largs[0] == self:largs = ()if self._refresh_text_from_property_ev is not None:self._refresh_text_from_property_ev.cancel()self._refresh_text_from_property_ev = Clock.schedule_once(lambda dt: self._refresh_text_from_property(*largs))def _update_text_options(self, *largs):Cache_remove('textinput.width')self._trigger_refresh_text()def _refresh_text_from_trigger(self, dt, *largs):self._refresh_text_from_property(*largs)def _refresh_text_from_property(self, *largs):self._refresh_text(self.text, *largs)def _refresh_text(self, text, *largs):# Refresh all the lines from a new text.# By using cache in internal functions, this method should be fast.mode = 'all'if len(largs) > 1:mode, start, finish, _lines, _lines_flags, len_lines = largs# start = max(0, start)cursor = Noneelse:cursor = self.cursor_index()_lines, self._lines_flags = self._split_smart(text)_lines_labels = []_line_rects = []_create_label = self._create_line_labelfor x in _lines:lbl = _create_label(x)_lines_labels.append(lbl)_line_rects.append(Rectangle(size=lbl.size))if mode == 'all':self._lines_labels = _lines_labelsself._lines_rects = _line_rectsself._lines[:] = _lineselif mode == 'del':if finish > start:self._insert_lines(start,finish if start == finish else (finish + 1),len_lines, _lines_flags,_lines, _lines_labels, _line_rects)elif mode == 'insert':self._insert_lines(start,finish if (start == finish and not len_lines)else (finish + 1),len_lines, _lines_flags, _lines, _lines_labels,_line_rects)min_line_ht = self._label_cached.get_extents('_')[1]# with markup texture can be of height `1`self.line_height = max(_lines_labels[0].height, min_line_ht)# self.line_spacing = 2# now, if the text change, maybe the cursor is not at the same place as# before. so, try to set the cursor on the good placerow = self.cursor_rowself.cursor = self.get_cursor_from_index(self.cursor_index()if cursor is None else cursor)# if we back to a new line, reset the scroll, otherwise, the effect is# uglyif self.cursor_row != row:self.scroll_x = 0# with the new text don't forget to update graphics againself._trigger_update_graphics()def _insert_lines(self, start, finish, len_lines, _lines_flags,_lines, _lines_labels, _line_rects):self_lines_flags = self._lines_flags_lins_flags = []_lins_flags.extend(self_lines_flags[:start])if len_lines:# if not inserting at first line thenif start:# make sure line flags restored for first line# _split_smart assumes first line to be not a new line_lines_flags[0] = self_lines_flags[start]_lins_flags.extend(_lines_flags)_lins_flags.extend(self_lines_flags[finish:])self._lines_flags = _lins_flags_lins_lbls = []_lins_lbls.extend(self._lines_labels[:start])if len_lines:_lins_lbls.extend(_lines_labels)_lins_lbls.extend(self._lines_labels[finish:])self._lines_labels = _lins_lbls_lins_rcts = []_lins_rcts.extend(self._lines_rects[:start])if len_lines:_lins_rcts.extend(_line_rects)_lins_rcts.extend(self._lines_rects[finish:])self._lines_rects = _lins_rcts_lins = []_lins.extend(self._lines[:start])if len_lines:_lins.extend(_lines)_lins.extend(self._lines[finish:])self._lines[:] = _linsdef _trigger_update_graphics(self, *largs):self._update_graphics_ev.cancel()self._update_graphics_ev()def _update_graphics(self, *largs):# Update all the graphics according to the current internal values.## This is a little bit complex, cause we have to :#     - handle scroll_x#     - handle padding#     - create rectangle for the lines matching the viewport#     - crop the texture coordinates to match the viewport## This is the first step of graphics, the second is the selection.self.canvas.clear()add = self.canvas.addlh = self.line_heightdy = lh + self.line_spacing# adjust view if the cursor is going outside the boundssx = self.scroll_xsy = self.scroll_y# draw labelsif not self._lines or (not self._lines[0] and len(self._lines) == 1):rects = self._hint_text_rectslabels = self._hint_text_labelslines = self._hint_text_lineselse:rects = self._lines_rectslabels = self._lines_labelslines = self._linespadding_left, padding_top, padding_right, padding_bottom = self.paddingx = self.x + padding_lefty = self.top - padding_top + syminy = self.y + padding_bottommaxy = self.top - padding_tophalign = self.halignbase_dir = self.base_directionfind_base_dir = Label.find_base_directionauto_halign_r = halign == 'auto' and base_dir and 'rtl' in base_dirfst_visible_ln = Nonefor line_num, value in enumerate(lines):if miny < y < maxy + dy:if fst_visible_ln is None:fst_visible_ln = line_numtexture = labels[line_num]size = list(texture.size)texc = texture.tex_coords[:]# calcul coordinateviewport_pos = sx, 0vw = self.width - padding_left - padding_rightvh = self.height - padding_top - padding_bottomtw, th = list(map(float, size))oh, ow = tch, tcw = texc[1:3]tcx, tcy = 0, 0# adjust size/texcoord according to viewportif viewport_pos:tcx, tcy = viewport_postcx = tcx / tw * (ow)tcy = tcy / th * ohif tw - viewport_pos[0] < vw:tcw = tcw - tcxsize[0] = tcw * size[0]elif vw < tw:tcw = (vw / tw) * tcwsize[0] = vwif vh < th:tch = (vh / th) * tchsize[1] = vh# croppingmlh = lhif y > maxy:vh = (maxy - y + lh)tch = (vh / float(lh)) * ohtcy = oh - tchsize[1] = vhif y - lh < miny:diff = miny - (y - lh)y += diffvh = lh - difftch = (vh / float(lh)) * ohsize[1] = vhtexc = (tcx,tcy + tch,tcx + tcw,tcy + tch,tcx + tcw,tcy,tcx,tcy)# Horizontal alignmentxoffset = 0if not base_dir:base_dir = self._resolved_base_dir = find_base_dir(value)if base_dir and halign == 'auto':auto_halign_r = 'rtl' in base_dirif halign == 'center':xoffset = int((vw - size[0]) / 2.)elif halign == 'right' or auto_halign_r:xoffset = max(0, int(vw - size[0]))# add rectangle.r = rects[line_num]r.pos = int(xoffset + x), int(y - mlh)r.size = sizer.texture = texturer.tex_coords = texcadd(r)elif y <= miny:line_num -= 1breaky -= dyif fst_visible_ln is not None:self._visible_lines_range = (fst_visible_ln, line_num + 1)else:self._visible_lines_range = 0, 0self._update_graphics_selection()def _update_graphics_selection(self):if not self._selection:returnself.canvas.remove_group('selection')dy = self.line_height + self.line_spacingrects = self._lines_rectspadding_top = self.padding[1]padding_bottom = self.padding[3]_top = self.topy = _top - padding_top + self.scroll_yminy = self.y + padding_bottommaxy = _top - padding_topdraw_selection = self._draw_selectiona, b = self._selection_from, self._selection_toif a > b:a, b = b, aget_cursor_from_index = self.get_cursor_from_indexs1c, s1r = get_cursor_from_index(a)s2c, s2r = get_cursor_from_index(b)s2r += 1# pass only the selection lines[]# passing all the lines can get slow when dealing with a lot of texty -= s1r * dy_lines = self._lines_get_text_width = self._get_text_widthtab_width = self.tab_width_label_cached = self._label_cachedwidth = self.widthpadding_left = self.padding[0]padding_right = self.padding[2]x = self.xcanvas_add = self.canvas.addselection_color = self.selection_colorfor line_num, value in enumerate(_lines[s1r:s2r], start=s1r):if miny <= y <= maxy + dy:r = rects[line_num]draw_selection(r.pos, r.size, line_num, (s1c, s1r),(s2c, s2r - 1), _lines, _get_text_width,tab_width, _label_cached, width,padding_left, padding_right, x,canvas_add, selection_color)y -= dyself._position_handles('both')def _draw_selection(self, *largs):pos, size, line_num, (s1c, s1r), (s2c, s2r),\_lines, _get_text_width, tab_width, _label_cached, width,\padding_left, padding_right, x, canvas_add, selection_color = largs# Draw the current selection on the widget.if line_num < s1r or line_num > s2r:returnx, y = posw, h = sizex1 = xx2 = x + wif line_num == s1r:lines = _lines[line_num]x1 -= self.scroll_xx1 += _get_text_width(lines[:s1c], tab_width, _label_cached)if line_num == s2r:lines = _lines[line_num]x2 = (x - self.scroll_x) + _get_text_width(lines[:s2c],tab_width,_label_cached)width_minus_padding = width - (padding_right + padding_left)maxx = x + width_minus_paddingif x1 > maxx:returnx1 = max(x1, x)x2 = min(x2, x + width_minus_padding)canvas_add(Color(*selection_color, group='selection'))canvas_add(Rectangle(pos=(x1, pos[1]), size=(x2 - x1, size[1]), group='selection'))def on_size(self, instance, value):# if the size change, we might do invalid scrolling / text split# size the text maybe be put after size_hint have been resolved.self._trigger_refresh_text()self._refresh_hint_text()self.scroll_x = self.scroll_y = 0def _get_row_width(self, row):# Get the pixel width of the given row._labels = self._lines_labelsif row < len(_labels):return _labels[row].widthreturn 0def _get_cursor_pos(self):# return the current cursor x/y from the row/coldy = self.line_height + self.line_spacingpadding_left = self.padding[0]padding_top = self.padding[1]padding_right = self.padding[2]left = self.x + padding_lefttop = self.top - padding_topy = top + self.scroll_yy -= self.cursor_row * dy# Horizontal alignmenthalign = self.halignviewport_width = self.width - padding_left - padding_rightcursor_offset = self.cursor_offset()base_dir = self.base_direction or self._resolved_base_dirauto_halign_r = halign == 'auto' and base_dir and 'rtl' in base_dirif halign == 'center':row_width = self._get_row_width(self.cursor_row)x = left + int((viewport_width - row_width) / 2) \+ cursor_offset - self.scroll_xelif halign == 'right' or auto_halign_r:row_width = self._get_row_width(self.cursor_row)x = left + viewport_width - row_width \+ cursor_offset - self.scroll_xelse:x = left + cursor_offset - self.scroll_xreturn x, ydef _get_cursor_visual_height(self):# Return the height of the cursor's visible part_, cy = map(int, self.cursor_pos)max_y = self.top - self.padding[1]min_y = self.y + self.padding[3]lh = self.line_heightif cy > max_y:return lh - min(lh, cy - max_y)else:return min(lh, max(0, cy - min_y))def _get_cursor_visual_pos(self):# Return the position of the cursor's top visible pointcx, cy = map(int, self.cursor_pos)max_y = self.top - self.padding[3]return [cx, min(max_y, cy)]def _get_line_options(self):# Get or create line options, to be used for Label creationif self._line_options is None:self._line_options = kw = {'font_size': self.font_size,'font_name': self.font_name,'font_context': self.font_context,'font_family': self.font_family,'text_language': self.text_language,'base_direction': self.base_direction,'anchor_x': 'left','anchor_y': 'top','padding_x': 0,'padding_y': 0,'padding': (0, 0)}self._label_cached = Label(**kw)return self._line_optionsdef _create_line_label(self, text, hint=False):# Create a label from a text, using line optionsntext = text.replace(u'\n', u'').replace(u'\t', u' ' * self.tab_width)if self.password and not hint:  # Don't replace hint_text with *ntext = self.password_mask * len(ntext)kw = self._get_line_options()cid = '%s\0%s' % (ntext, str(kw))texture = Cache_get('textinput.label', cid)if texture is None:# FIXME right now, we can't render very long line...# if we move on "VBO" version as fallback, we won't need to# do this. try to found the maximum text we can handlelabel = Nonelabel_len = len(ntext)ld = None# check for blank lineif not ntext:texture = Texture.create(size=(1, 1))Cache_append('textinput.label', cid, texture)return texturewhile True:try:label = Label(text=ntext[:label_len], **kw)label.refresh()if ld is not None and ld > 2:ld = int(ld / 2)label_len += ldelse:breakexcept:# exception happen when we tried to render the text# reduce it...if ld is None:ld = len(ntext)ld = int(ld / 2)if ld < 2 and label_len:label_len -= 1label_len -= ldcontinue# ok, we found it.texture = label.textureCache_append('textinput.label', cid, texture)return texturedef _tokenize(self, text):# Tokenize a text string from some delimitersif text is None:returndelimiters = u' ,\'".;:\n\r\t'oldindex = 0for index, char in enumerate(text):if char not in delimiters:continueif oldindex != index:yield text[oldindex:index]yield text[index:index + 1]oldindex = index + 1yield text[oldindex:]def _split_smart(self, text):# Do a "smart" split. If autowidth or autosize is set,# we are not doing smart split, just a split on line break.# Otherwise, we are trying to split as soon as possible, to prevent# overflow on the widget.# depend of the options, split the text on line, or wordif not self.multiline:lines = text.split(u'\n')lines_flags = [0] + [FL_IS_LINEBREAK] * (len(lines) - 1)return lines, lines_flags# no autosize, do wordwrap.x = flags = 0line = []lines = []lines_flags = []_join = u''.joinlines_append, lines_flags_append = lines.append, lines_flags.appendpadding_left = self.padding[0]padding_right = self.padding[2]width = self.width - padding_left - padding_righttext_width = self._get_text_width_tab_width, _label_cached = self.tab_width, self._label_cached# try to add each word on current line.for word in self._tokenize(text):is_newline = (word == u'\n')w = text_width(word, _tab_width, _label_cached)# if we have more than the width, or if it's a newline,# push the current line, and create a new oneif (x + w > width and line) or is_newline:lines_append(_join(line))lines_flags_append(flags)flags = 0line = []x = 0if is_newline:flags |= FL_IS_LINEBREAKelif width >= 1 and w > width:while w > width:split_width = split_pos = 0# split the wordfor c in word:cw = self._get_text_width(c, self.tab_width, self._label_cached)if split_width + cw > width:breaksplit_width += cwsplit_pos += 1if split_width == split_pos == 0:# can't fit the word in, give upbreaklines_append(word[:split_pos])lines_flags_append(flags)flags = FL_IS_WORDBREAKword = word[split_pos:]w -= split_widthx = wline.append(word)else:x += wline.append(word)if line or flags & FL_IS_LINEBREAK:lines_append(_join(line))lines_flags_append(flags)return lines, lines_flagsdef _key_down(self, key, repeat=False):displayed_str, internal_str, internal_action, scale = key# handle deletionif (self._selection andinternal_action in (None, 'del', 'backspace', 'enter')):if internal_action != 'enter' or self.multiline:self.delete_selection()elif internal_action == 'del':# Move cursor one char to the right. If that was successful,# do a backspace (effectively deleting char right of cursor)cursor = self.cursorself.do_cursor_movement('cursor_right')if cursor != self.cursor:self.do_backspace(mode='del')elif internal_action == 'backspace':self.do_backspace()# handle action keys and text insertionif internal_action is None:self.insert_text(displayed_str)elif internal_action in ('shift', 'shift_L', 'shift_R'):if not self._selection:self._selection_from = self._selection_to = self.cursor_index()self._selection = Trueself._selection_finished = Falseelif internal_action == 'ctrl_L':self._ctrl_l = Trueelif internal_action == 'ctrl_R':self._ctrl_r = Trueelif internal_action == 'alt_L':self._alt_l = Trueelif internal_action == 'alt_R':self._alt_r = Trueelif internal_action.startswith('cursor_'):cc, cr = self.cursorself.do_cursor_movement(internal_action,self._ctrl_l or self._ctrl_r,self._alt_l or self._alt_r)if self._selection and not self._selection_finished:self._selection_to = self.cursor_index()self._update_selection()else:self.cancel_selection()elif internal_action == 'enter':if self.multiline:self.insert_text(u'\n')else:self.dispatch('on_text_validate')if self.text_validate_unfocus:self.focus = Falseelif internal_action == 'escape':self.focus = Falsedef _key_up(self, key, repeat=False):displayed_str, internal_str, internal_action, scale = keyif internal_action in ('shift', 'shift_L', 'shift_R'):if self._selection:self._update_selection(True)elif internal_action == 'ctrl_L':self._ctrl_l = Falseelif internal_action == 'ctrl_R':self._ctrl_r = Falseelif internal_action == 'alt_L':self._alt_l = Falseelif internal_action == 'alt_R':self._alt_r = Falsedef keyboard_on_key_down(self, window, keycode, text, modifiers):# Keycodes on OS X:ctrl, cmd = 64, 1024key, key_str = keycodewin = EventLoop.window# This allows *either* ctrl *or* cmd, but not both.modifiers = set(modifiers) - {'capslock', 'numlock'}is_shortcut = (modifiers == {'ctrl'} or (_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 Trueif not self._editable:# duplicated but faster testing for non-editable keysif text and not is_interesting_key:if is_shortcut and key == ord('c'):self.copy()elif key == 27:self.focus = Falsereturn Trueif text and not is_interesting_key:self._hide_handles(win)self._hide_cut_copy_paste(win)win.remove_widget(self._handle_middle)# check for command modes# we use \x01INFO\x02 to get info from IME on mobiles# pygame seems to pass \x01 as the unicode for ctrl+a# checking for modifiers ensures conflict resolution.first_char = ord(text[0])if not modifiers and first_char == 1:self._command_mode = Trueself._command = ''if not modifiers and first_char == 2:self._command_mode = Falseself._command = self._command[1:]if self._command_mode:self._command += textreturn_command = self._commandif _command and first_char == 2:from_undo = True_command, data = _command.split(':')self._command = ''if self._selection:self.delete_selection()if _command == 'DEL':count = int(data)if not count:self.delete_selection(from_undo=True)end = self.cursor_index()self._selection_from = max(end - count, 0)self._selection_to = endself._selection = Trueself.delete_selection(from_undo=True)returnelif _command == 'INSERT':self.insert_text(data, from_undo)elif _command == 'INSERTN':from_undo = Falseself.insert_text(data, from_undo)elif _command == 'SELWORD':self.dispatch('on_double_tap')elif _command == 'SEL':if data == '0':Clock.schedule_once(lambda dt: self.cancel_selection())elif _command == 'CURCOL':self.cursor = int(data), self.cursor_rowreturnif is_shortcut:if key == ord('x'):  # cut selectionself._cut(self.selection_text)elif key == ord('c'):  # copy selectionself.copy()elif key == ord('v'):  # paste clipboard contentself.paste()elif key == ord('a'):  # select allself.select_all()elif key == ord('z'):  # undoself.do_undo()elif key == ord('r'):  # redoself.do_redo()else:is_sdl2 = (EventLoop.window.__class__.__module__ =='kivy.core.window.window_sdl2')if is_sdl2:# we expect to get managed key input via on_textinputreturnif self._selection:self.delete_selection()self.insert_text(text)# self._recalc_size()returnif is_interesting_key:self._hide_cut_copy_paste(win)self._hide_handles(win)if key == 27:  # escapeself.focus = Falsereturn Trueelif key == 9:  # tabself.insert_text(u'\t')return Truek = self.interesting_keys.get(key)if k:key = (None, None, k, 1)self._key_down(key)def keyboard_on_key_up(self, window, keycode):key, key_str = keycodek = self.interesting_keys.get(key)if k:key = (None, None, k, 1)self._key_up(key)def keyboard_on_textinput(self, window, text):if self._selection:self.delete_selection()self.insert_text(text, False)# current IME composition in progress by the IME system, or '' if nothing_ime_composition = StringProperty('')# cursor position of last IME event_ime_cursor = ListProperty(None, allownone=True)def _bind_keyboard(self):super()._bind_keyboard()Window.bind(on_textedit=self.window_on_textedit)def _unbind_keyboard(self):super()._unbind_keyboard()Window.unbind(on_textedit=self.window_on_textedit)def window_on_textedit(self, window, ime_input):text_lines = self._lines or ['']if self._ime_composition:pcc, pcr = self._ime_cursortext = text_lines[pcr]len_ime = len(self._ime_composition)if text[pcc - len_ime:pcc] == self._ime_composition:  # always?remove_old_ime_text = text[:pcc - len_ime] + text[pcc:]ci = self.cursor_index()self._refresh_text_from_property("insert",*self._get_line_from_cursor(pcr, remove_old_ime_text))self.cursor = self.get_cursor_from_index(ci - len_ime)if ime_input:if self._selection:self.delete_selection()cc, cr = self.cursortext = text_lines[cr]new_text = text[:cc] + ime_input + text[cc:]self._refresh_text_from_property("insert", *self._get_line_from_cursor(cr, new_text))self.cursor = self.get_cursor_from_index(self.cursor_index() + len(ime_input))self._ime_composition = ime_inputself._ime_cursor = self.cursordef on__hint_text(self, instance, value):self._refresh_hint_text()def _refresh_hint_text(self):_lines, self._hint_text_flags = self._split_smart(self.hint_text)_hint_text_labels = []_hint_text_rects = []_create_label = self._create_line_labelfor x in _lines:lbl = _create_label(x, hint=True)_hint_text_labels.append(lbl)_hint_text_rects.append(Rectangle(size=lbl.size))self._hint_text_lines[:] = _linesself._hint_text_labels = _hint_text_labelsself._hint_text_rects = _hint_text_rects# Remember to update graphicsself._trigger_update_graphics()## Properties#_lines = ListProperty([])_hint_text_lines = ListProperty([])_editable = BooleanProperty(True)_insert_int_pat = re.compile(u'^-?[0-9]*$')_insert_float_pat = re.compile(u'^-?[0-9]*\\.?[0-9]*$')_cursor_blink = BooleanProperty(False)_cursor_visual_pos = AliasProperty(_get_cursor_visual_pos, None, bind=['cursor_pos'])_cursor_visual_height = AliasProperty(_get_cursor_visual_height, None, bind=['cursor_pos'])readonly = BooleanProperty(False)'''If True, the user will not be able to change the content of a textinput... versionadded:: 1.3.0:attr:`readonly` is a :class:`~kivy.properties.BooleanProperty` anddefaults to False.'''text_validate_unfocus = BooleanProperty(True)'''If True, the :meth:`TextInput.on_text_validate` event will unfocus thewidget, therefore make it stop listening to the keyboard. When disabled,the :meth:`TextInput.on_text_validate` event can be fired multiple timesas the result of TextInput keeping the focus enabled... versionadded:: 1.10.1:attr:`text_validate_unfocus` isa :class:`~kivy.properties.BooleanProperty` and defaults to True.'''multiline = BooleanProperty(True)'''If True, the widget will be able show multiple lines of text. If False,the "enter" keypress will defocus the textinput instead of adding a newline.:attr:`multiline` is a :class:`~kivy.properties.BooleanProperty` anddefaults to True.'''password = BooleanProperty(False)'''If True, the widget will display its characters as the characterset in :attr:`password_mask`... versionadded:: 1.2.0:attr:`password` is a :class:`~kivy.properties.BooleanProperty` anddefaults to False.'''password_mask = StringProperty('*')'''Sets the character used to mask the text when :attr:`password` is True... versionadded:: 1.10.0:attr:`password_mask` is a :class:`~kivy.properties.StringProperty` anddefaults to `'*'`.'''keyboard_suggestions = BooleanProperty(True)'''If True provides auto suggestions on top of keyboard.This will only work if :attr:`input_type` is set to `text`... versionadded:: 1.8.0:attr:`keyboard_suggestions` is a :class:`~kivy.properties.BooleanProperty`and defaults to True.'''cursor_blink = BooleanProperty(True)'''This property is used to set whether the graphic cursor should blinkor not... versionchanged:: 1.10.1`cursor_blink` has been refactored to enable switching the blinkingon/off and the previous behavior has been moved to a private`_cursor_blink` property. The previous default value `False` has beenchanged to `True`.:attr:`cursor_blink` is a :class:`~kivy.properties.BooleanProperty` anddefaults to True.'''def _get_cursor(self):return self._cursordef _set_cursor(self, pos):if not self._lines:self._trigger_refresh_text()returnl = self._linescr = boundary(pos[1], 0, len(l) - 1)cc = boundary(pos[0], 0, len(l[cr]))cursor = cc, cr# adjust scrollview to ensure that the cursor will be always inside our# viewport.padding_left = self.padding[0]padding_right = self.padding[2]viewport_width = self.width - padding_left - padding_rightsx = self.scroll_xoffset = self.cursor_offset()# if offset is outside the current bounds, readjustif offset > viewport_width + sx:self.scroll_x = offset - viewport_widthif offset < sx:self.scroll_x = offset# do the same for Y# this algo try to center the cursor as much as possibledy = self.line_height + self.line_spacingoffsety = cr * dysy = self.scroll_ypadding_top = self.padding[1]padding_bottom = self.padding[3]viewport_height = self.height - padding_top - padding_bottom - dyif offsety > viewport_height + sy:sy = offsety - viewport_heightif offsety < sy:sy = offsetyself.scroll_y = syif self._cursor == cursor:returnself._cursor = cursorreturn Truecursor = AliasProperty(_get_cursor, _set_cursor)'''Tuple of (col, row) values indicating the current cursor position.You can set a new (col, row) if you want to move the cursor. The scrollingarea will be automatically updated to ensure that the cursor isvisible inside the viewport.:attr:`cursor` is an :class:`~kivy.properties.AliasProperty`.'''def _get_cursor_col(self):return self._cursor[0]cursor_col = AliasProperty(_get_cursor_col, None, bind=('cursor', ))'''Current column of the cursor.:attr:`cursor_col` is an :class:`~kivy.properties.AliasProperty` tocursor[0], read-only.'''def _get_cursor_row(self):return self._cursor[1]cursor_row = AliasProperty(_get_cursor_row, None, bind=('cursor', ))'''Current row of the cursor.:attr:`cursor_row` is an :class:`~kivy.properties.AliasProperty` tocursor[1], read-only.'''cursor_pos = AliasProperty(_get_cursor_pos, None,bind=('cursor', 'padding', 'pos', 'size','focus', 'scroll_x', 'scroll_y','line_height', 'line_spacing'),cache=False)'''Current position of the cursor, in (x, y).:attr:`cursor_pos` is an :class:`~kivy.properties.AliasProperty`,read-only.'''cursor_color = ColorProperty([1, 0, 0, 1])'''Current color of the cursor, in (r, g, b, a) format... versionadded:: 1.9.0:attr:`cursor_color` is a :class:`~kivy.properties.ColorProperty` anddefaults to [1, 0, 0, 1]... versionchanged:: 2.0.0Changed from :class:`~kivy.properties.ListProperty` to:class:`~kivy.properties.ColorProperty`.'''cursor_width = NumericProperty('1sp')'''Current width of the cursor... versionadded:: 1.10.0:attr:`cursor_width` is a :class:`~kivy.properties.NumericProperty` anddefaults to '1sp'.'''line_height = NumericProperty(1)'''Height of a line. This property is automatically computed from the:attr:`font_name`, :attr:`font_size`. Changing the line_height will haveno impact... note:::attr:`line_height` is the height of a single line of text.Use :attr:`minimum_height`, which also includes padding, toget the height required to display the text properly.:attr:`line_height` is a :class:`~kivy.properties.NumericProperty`,read-only.'''tab_width = NumericProperty(4)'''By default, each tab will be replaced by four spaces on the textinput widget. You can set a lower or higher value.:attr:`tab_width` is a :class:`~kivy.properties.NumericProperty` anddefaults to 4.'''padding_x = VariableListProperty([0, 0], length=2, deprecated=True)'''Horizontal padding of the text: [padding_left, padding_right].padding_x also accepts a one argument form [padding_horizontal].:attr:`padding_x` is a :class:`~kivy.properties.VariableListProperty` anddefaults to [0, 0]. This might be changed by the current theme... deprecated:: 1.7.0Use :attr:`padding` instead.'''def on_padding_x(self, instance, value):self.padding[0] = value[0]self.padding[2] = value[1]padding_y = VariableListProperty([0, 0], length=2, deprecated=True)'''Vertical padding of the text: [padding_top, padding_bottom].padding_y also accepts a one argument form [padding_vertical].:attr:`padding_y` is a :class:`~kivy.properties.VariableListProperty` anddefaults to [0, 0]. This might be changed by the current theme... deprecated:: 1.7.0Use :attr:`padding` instead.'''def on_padding_y(self, instance, value):self.padding[1] = value[0]self.padding[3] = value[1]padding = VariableListProperty([6, 6, 6, 6])'''Padding of the text: [padding_left, padding_top, padding_right,padding_bottom].padding also accepts a two argument form [padding_horizontal,padding_vertical] and a one argument form [padding]... versionchanged:: 1.7.0Replaced AliasProperty with VariableListProperty.:attr:`padding` is a :class:`~kivy.properties.VariableListProperty` anddefaults to [6, 6, 6, 6].'''halign = OptionProperty('auto', options=['left', 'center', 'right','auto'])'''Horizontal alignment of the text.:attr:`halign` is an :class:`~kivy.properties.OptionProperty` anddefaults to 'auto'. Available options are : auto, left, center and right.Auto will attempt to autodetect horizontal alignment for RTL text (Pangoonly), otherwise it behaves like `left`... versionadded:: 1.10.1'''scroll_x = NumericProperty(0)'''X scrolling value of the viewport. The scrolling is automaticallyupdated when the cursor is moved or text changed. If there is nouser input, the scroll_x and scroll_y properties may be changed.:attr:`scroll_x` is a :class:`~kivy.properties.NumericProperty` anddefaults to 0.'''scroll_y = NumericProperty(0)'''Y scrolling value of the viewport. See :attr:`scroll_x` for moreinformation.:attr:`scroll_y` is a :class:`~kivy.properties.NumericProperty` anddefaults to 0.'''selection_color = ColorProperty([0.1843, 0.6549, 0.8313, .5])'''Current color of the selection, in (r, g, b, a) format... warning::The color should always have an "alpha" component less than 1since the selection is drawn after the text.:attr:`selection_color` is a :class:`~kivy.properties.ColorProperty` anddefaults to [0.1843, 0.6549, 0.8313, .5]... versionchanged:: 2.0.0Changed from :class:`~kivy.properties.ListProperty` to:class:`~kivy.properties.ColorProperty`.'''border = ListProperty([4, 4, 4, 4])'''Border used for :class:`~kivy.graphics.vertex_instructions.BorderImage`graphics instruction. Used with :attr:`background_normal` and:attr:`background_active`. Can be used for a custom background... versionadded:: 1.4.1It must be a list of four values: (bottom, right, top, left). Read theBorderImage instruction for more information about how to use it.:attr:`border` is a :class:`~kivy.properties.ListProperty` and defaultsto (4, 4, 4, 4).'''background_normal = StringProperty('atlas://data/images/defaulttheme/textinput')'''Background image of the TextInput when it's not in focus... versionadded:: 1.4.1:attr:`background_normal` is a :class:`~kivy.properties.StringProperty` anddefaults to 'atlas://data/images/defaulttheme/textinput'.'''background_disabled_normal = StringProperty('atlas://data/images/defaulttheme/textinput_disabled')'''Background image of the TextInput when disabled... versionadded:: 1.8.0:attr:`background_disabled_normal` is a:class:`~kivy.properties.StringProperty` anddefaults to 'atlas://data/images/defaulttheme/textinput_disabled'.'''background_active = StringProperty('atlas://data/images/defaulttheme/textinput_active')'''Background image of the TextInput when it's in focus... versionadded:: 1.4.1:attr:`background_active` is a:class:`~kivy.properties.StringProperty` anddefaults to 'atlas://data/images/defaulttheme/textinput_active'.'''background_color = ColorProperty([1, 1, 1, 1])'''Current color of the background, in (r, g, b, a) format... versionadded:: 1.2.0:attr:`background_color` is a :class:`~kivy.properties.ColorProperty`and defaults to [1, 1, 1, 1] (white)... versionchanged:: 2.0.0Changed from :class:`~kivy.properties.ListProperty` to:class:`~kivy.properties.ColorProperty`.'''foreground_color = ColorProperty([0, 0, 0, 1])'''Current color of the foreground, in (r, g, b, a) format... versionadded:: 1.2.0:attr:`foreground_color` is a :class:`~kivy.properties.ColorProperty`and defaults to [0, 0, 0, 1] (black)... versionchanged:: 2.0.0Changed from :class:`~kivy.properties.ListProperty` to:class:`~kivy.properties.ColorProperty`.'''disabled_foreground_color = ColorProperty([0, 0, 0, .5])'''Current color of the foreground when disabled, in (r, g, b, a) format... versionadded:: 1.8.0:attr:`disabled_foreground_color` is a:class:`~kivy.properties.ColorProperty` anddefaults to [0, 0, 0, 5] (50% transparent black)... versionchanged:: 2.0.0Changed from :class:`~kivy.properties.ListProperty` to:class:`~kivy.properties.ColorProperty`.'''use_bubble = BooleanProperty(not _is_desktop)'''Indicates whether the cut/copy/paste bubble is used... versionadded:: 1.7.0:attr:`use_bubble` is a :class:`~kivy.properties.BooleanProperty`and defaults to True on mobile OS's, False on desktop OS's.'''use_handles = BooleanProperty(not _is_desktop)'''Indicates whether the selection handles are displayed... versionadded:: 1.8.0:attr:`use_handles` is a :class:`~kivy.properties.BooleanProperty`and defaults to True on mobile OS's, False on desktop OS's.'''suggestion_text = StringProperty('')'''Shows a suggestion text at the end of the current line.The feature is useful for text autocompletion, and it does not implementvalidation (accepting the suggested text on enter etc.).This can also be used by the IME to setup the current word being edited... versionadded:: 1.9.0:attr:`suggestion_text` is a :class:`~kivy.properties.StringProperty` anddefaults to `''`.'''def on_suggestion_text(self, instance, value):global MarkupLabelif not MarkupLabel:from kivy.core.text.markup import MarkupLabelcursor_row = self.cursor_rowif cursor_row >= len(self._lines) or self.canvas is None:returncursor_pos = self.cursor_postxt = self._lines[cursor_row]kw = self._get_line_options()rct = self._lines_rects[cursor_row]lbl = text = Noneif value:lbl = MarkupLabel(text=txt + "[b]{}[/b]".format(value), **kw)else:lbl = Label(**kw)text = txtlbl.refresh()self._lines_labels[cursor_row] = lbl.texturerct.size = lbl.sizeself._update_graphics()def get_sel_from(self):return self._selection_fromselection_from = AliasProperty(get_sel_from, None)'''If a selection is in progress or complete, this property will representthe cursor index where the selection started... versionchanged:: 1.4.0:attr:`selection_from` is an :class:`~kivy.properties.AliasProperty`and defaults to None, readonly.'''def get_sel_to(self):return self._selection_toselection_to = AliasProperty(get_sel_to, None)'''If a selection is in progress or complete, this property will representthe cursor index where the selection started... versionchanged:: 1.4.0:attr:`selection_to` is an :class:`~kivy.properties.AliasProperty` anddefaults to None, readonly.'''selection_text = StringProperty(u'')'''Current content selection.:attr:`selection_text` is a :class:`~kivy.properties.StringProperty`and defaults to '', readonly.'''def on_selection_text(self, instance, value):if value:if self.use_handles:self._trigger_show_handles()if CutBuffer and not self.password:self._trigger_update_cutbuffer()def _get_text(self):flags = self._lines_flagslines = self._lineslen_lines = len(lines)less_flags = len(flags) < len_linesif less_flags:flags.append(1)text = ''.join(('\n' if (flags[i] & FL_IS_LINEBREAK) else '') + lines[i]for i in range(len_lines))if less_flags:flags.pop()return textdef _set_text(self, text):if isinstance(text, bytes):text = text.decode('utf8')if self.replace_crlf:text = text.replace(u'\r\n', u'\n')if self.text != text:self._refresh_text(text)self.cursor = self.get_cursor_from_index(len(text))text = AliasProperty(_get_text, _set_text, bind=('_lines',), cache=True)'''Text of the widget.Creation of a simple hello world::widget = TextInput(text='Hello world')If you want to create the widget with an unicode string, use::widget = TextInput(text=u'My unicode string'):attr:`text` is an :class:`~kivy.properties.AliasProperty`.'''font_name = StringProperty(DEFAULT_FONT)'''Filename of the font to use. The path can be absolute or relative.Relative paths are resolved by the :func:`~kivy.resources.resource_find`function... warning::Depending on your text provider, the font file may be ignored. However,you can mostly use this without problems.If the font used lacks the glyphs for the particular language/symbolsyou are using, you will see '[]' blank box characters instead of theactual glyphs. The solution is to use a font that has the glyphs youneed to display. For example, to display |unicodechar|, use a font likefreesans.ttf that has the glyph... |unicodechar| image:: images/unicode-char.png:attr:`font_name` is a :class:`~kivy.properties.StringProperty` anddefaults to 'Roboto'. This value is takenfrom :class:`~kivy.config.Config`.'''font_size = NumericProperty('15sp')'''Font size of the text in pixels.:attr:`font_size` is a :class:`~kivy.properties.NumericProperty` anddefaults to 15 :attr:`~kivy.metrics.sp`.'''font_context = StringProperty(None, allownone=True)'''Font context. `None` means the font is used in isolation, so you areguaranteed to be drawing with the TTF file resolved by :attr:`font_name`.Specifying a value here will load the font file into a named context,enabling fallback between all fonts in the same context. If a fontcontext is set, you are not guaranteed that rendering will actually usethe specified TTF file for all glyphs (Pango will pick the one itthinks is best).If Kivy is linked against a system-wide installation of FontConfig,you can load the system fonts by specifying a font context startingwith the special string `system://`. This will load the systemfontconfig configuration, and add your application-specific fonts ontop of it (this imposes a signifficant risk of family name collision,Pango may not use your custom font file, but pick one from the system).. note::This feature requires the Pango text provider... versionadded:: 1.10.1:attr:`font_context` is a :class:`~kivy.properties.StringProperty` anddefaults to None.'''font_family = StringProperty(None, allownone=True)'''Font family, this is only applicable when using :attr:`font_context`option. The specified font family will be requested, but note that it maynot be available, or there could be multiple fonts registered with thesame family. The value can be a family name (string) available in thefont context (for example a system font in a `system://` context, or acustom font file added using :class:`kivy.core.text.FontContextManager`).If set to `None`, font selection is controlled by the :attr:`font_name`setting... note::If using :attr:`font_name` to reference a custom font file, youshould leave this as `None`. The family name is managed automaticallyin this case... note::This feature requires the Pango text provider... versionadded:: 1.10.1:attr:`font_family` is a :class:`~kivy.properties.StringProperty` anddefaults to None.'''base_direction = OptionProperty(None,options=['ltr', 'rtl', 'weak_rtl', 'weak_ltr', None],allownone=True)'''Base direction of text, this impacts horizontal alignment when:attr:`halign` is `auto` (the default). Available options are: None,"ltr" (left to right), "rtl" (right to left) plus "weak_ltr" and"weak_rtl"... note::This feature requires the Pango text provider... note::Weak modes are currently not implemented in Kivy text layout, andhave the same effect as setting strong mode... versionadded:: 1.10.1:attr:`base_direction` is an :class:`~kivy.properties.OptionProperty` anddefaults to None (autodetect RTL if possible, otherwise LTR).'''text_language = StringProperty(None, allownone=True)'''Language of the text, if None Pango will determine it from locale.This is an RFC-3066 format language tag (as a string), for example"en_US", "zh_CN", "fr" or "ja". This can impact font selection, metricsand rendering. For example, the same bytes of text can look differentfor `ur` and `ar` languages, though both use Arabic script... note::This feature requires the Pango text provider... versionadded:: 1.10.1:attr:`text_language` is a :class:`~kivy.properties.StringProperty` anddefaults to None.'''_hint_text = StringProperty('')def _set_hint_text(self, value):if isinstance(value, bytes):value = value.decode('utf8')self._hint_text = valuedef _get_hint_text(self):return self._hint_texthint_text = AliasProperty(_get_hint_text, _set_hint_text, bind=('_hint_text', ))'''Hint text of the widget, shown if text is ''... versionadded:: 1.6.0.. versionchanged:: 1.10.0The property is now an AliasProperty and byte values are decoded tostrings. The hint text will stay visible when the widget is focused.:attr:`hint_text` a :class:`~kivy.properties.AliasProperty` and defaultsto ''.'''hint_text_color = ColorProperty([0.5, 0.5, 0.5, 1.0])'''Current color of the hint_text text, in (r, g, b, a) format... versionadded:: 1.6.0:attr:`hint_text_color` is a :class:`~kivy.properties.ColorProperty` anddefaults to [0.5, 0.5, 0.5, 1.0] (grey)... versionchanged:: 2.0.0Changed from :class:`~kivy.properties.ListProperty` to:class:`~kivy.properties.ColorProperty`.'''auto_indent = BooleanProperty(False)'''Automatically indent multiline text... versionadded:: 1.7.0:attr:`auto_indent` is a :class:`~kivy.properties.BooleanProperty` anddefaults to False.'''replace_crlf = BooleanProperty(True)'''Automatically replace CRLF with LF... versionadded:: 1.9.1:attr:`replace_crlf` is a :class:`~kivy.properties.BooleanProperty` anddefaults to True.'''allow_copy = BooleanProperty(True)'''Decides whether to allow copying the text... versionadded:: 1.8.0:attr:`allow_copy` is a :class:`~kivy.properties.BooleanProperty` anddefaults to True.'''def _get_min_height(self):return (len(self._lines) * (self.line_height + self.line_spacing) +self.padding[1] + self.padding[3])minimum_height = AliasProperty(_get_min_height,bind=('_lines', 'line_spacing', 'padding','font_size', 'font_name', 'password','font_context', 'hint_text','line_height'),cache=True)'''Minimum height of the content inside the TextInput... versionadded:: 1.8.0:attr:`minimum_height` is a readonly:class:`~kivy.properties.AliasProperty`... warning:::attr:`minimum_width` is calculated based on :attr:`width` thereforecode like this will lead to an infinite loop::<FancyTextInput>:height: self.minimum_heightwidth: self.height'''line_spacing = NumericProperty(0)'''Space taken up between the lines... versionadded:: 1.8.0:attr:`line_spacing` is a :class:`~kivy.properties.NumericProperty` anddefaults to 0.'''input_filter = ObjectProperty(None, allownone=True)''' Filters the input according to the specified mode, if not None. IfNone, no filtering is applied... versionadded:: 1.9.0:attr:`input_filter` is an :class:`~kivy.properties.ObjectProperty` anddefaults to `None`. Can be one of `None`, `'int'` (string), or `'float'`(string), or a callable. If it is `'int'`, it will only accept numbers.If it is `'float'` it will also accept a single period. Finally, if it isa callable it will be called with two parameters; the string to be addedand a bool indicating whether the string is a result of undo (True). Thecallable should return a new substring that will be used instead.'''handle_image_middle = StringProperty('atlas://data/images/defaulttheme/selector_middle')'''Image used to display the middle handle on the TextInput for cursorpositioning... versionadded:: 1.8.0:attr:`handle_image_middle` is a :class:`~kivy.properties.StringProperty`and defaults to 'atlas://data/images/defaulttheme/selector_middle'.'''def on_handle_image_middle(self, instance, value):if self._handle_middle:self._handle_middle.source = valuehandle_image_left = StringProperty('atlas://data/images/defaulttheme/selector_left')'''Image used to display the Left handle on the TextInput for selection... versionadded:: 1.8.0:attr:`handle_image_left` is a :class:`~kivy.properties.StringProperty` anddefaults to 'atlas://data/images/defaulttheme/selector_left'.'''def on_handle_image_left(self, instance, value):if self._handle_left:self._handle_left.source = valuehandle_image_right = StringProperty('atlas://data/images/defaulttheme/selector_right')'''Image used to display the Right handle on the TextInput for selection... versionadded:: 1.8.0:attr:`handle_image_right` is a:class:`~kivy.properties.StringProperty` and defaults to'atlas://data/images/defaulttheme/selector_right'.'''def on_handle_image_right(self, instance, value):if self._handle_right:self._handle_right.source = valuewrite_tab = BooleanProperty(True)'''Whether the tab key should move focus to the next widget or if it shouldenter a tab in the :class:`TextInput`. If `True` a tab will be written,otherwise, focus will move to the next widget... versionadded:: 1.9.0:attr:`write_tab` is a :class:`~kivy.properties.BooleanProperty` anddefaults to `True`.'''if __name__ == '__main__':from kivy.app import Appfrom kivy.uix.boxlayout import BoxLayoutfrom kivy.lang import Builderclass TextInputApp(App):def build(self):Builder.load_string('''
<TextInput>on_text:self.suggestion_text = ''self.suggestion_text = 'ion_text'''')root = BoxLayout(orientation='vertical')textinput = TextInput(multiline=True, use_bubble=True,use_handles=True)# textinput.text = __doc__root.add_widget(textinput)textinput2 = TextInput(multiline=False, text='monoline textinput',size_hint=(1, None), height=30)root.add_widget(textinput2)return rootTextInputApp().run()

_get_text_width

_get_text_width 是 TextInput 的一个方法,用于获取文本在控件中的实际宽度。它的参数包括文本内容、制表符宽度和缓存标签。在上面的代码中,这个方法被用于计算 TextInput 控件中文本的水平偏移量。

 def _get_text_width(self, text, tab_width, _label_cached):# Return the width of a text, according to the current line optionskw = self._get_line_options()try:cid = u'{}\0{}\0{}'.format(text, self.password, kw)except UnicodeDecodeError:cid = '{}\0{}\0{}'.format(text, self.password, kw)width = Cache_get('textinput.width', cid)if width:return widthif not _label_cached:_label_cached = self._label_cachedtext = text.replace('\t', ' ' * tab_width)if not self.password:width = _label_cached.get_extents(text)[0]else:width = _label_cached.get_extents(self.password_mask * len(text))[0]Cache_append('textinput.width', cid, width)return width

实战:

        

TextInput:id: name_inputtext: 'C0C'size_hint_x: .5size_hint_y: Noneheight: '32dp'padding_x: [self.center[0]/6.0 - self._get_text_width(max(self._lines, key=len), self.tab_width, self._label_cached)/6.0, 0]padding_y: [self.height/2.0 - (self.line_height/2.0) * len(self._lines), 0]

这是一段Kivy的代码,是一个名为"name_input"的TextInput组件,其主要功能是用于用户输入文本。下面是各部分代码的作用介绍:

  • id: name_input:给TextInput组件设置了一个唯一的id,方便后续在代码中进行引用。

  • text: ‘C0C’:设置TextInput组件的默认文本为“C0C”。

  • size_hint_x: .5:设置组件在父容器中占据的宽度比例为0.5,即占据父容器宽度的50%。

  • size_hint_y: None:设置组件在父容器中占据的高度比例为None,即不进行自适应高度调整。

  • height: ‘32dp’:设置组件的默认高度为32dp。

  • padding_x: [self.center/6.0 - self._get_text_width(max(self._lines, key=len), self.tab_width, self._label_cached)/6.0, 0]:设置组件在水平方向上的内边距。这里使用了一些计算,以使得输入框左右两侧的内边距与文字的长度相关。

  • padding_y: [self.height/2.0 - (self.line_height/2.0) * len(self._lines), 0]:设置组件在垂直方向上的内边距。这里使用了一些计算,以使得输入框上下两侧的内边距与文字的行数和行高相关。

  • padding_x和padding_y是用来设置文本框内部文字的位置的属性。具体来说,

padding_x是一个长度为2的列表,

第一个元素表示文字距离文本框左侧的距离,

第二个元素表示文字距离文本框右侧的距离;

  • padding_y同理,

第一个元素表示文字距离文本框底部的距离,

第二个元素表示文字距离文本框顶部的距离。

在这段代码中,padding_x和padding_y的计算方式与文本框大小、文字行数、文字大小等因素有关。具体来说,padding_x的第一个元素的计算方式是通过将文本框宽度除以6,再减去文本的宽度除以6,最后再除以2得到的;padding_y的第一个元素的计算方式是将文本框高度除以2,再减去每行文字高度的一半再乘以文字行数得到的。这样计算出来的padding_x和padding_y可以保证文字在文本框内部居中显示。

tab_width

将tap键替换为指定数量的空格,默认为4

    tab_width = NumericProperty(4)'''By default, each tab will be replaced by four spaces on the textinput widget. You can set a lower or higher value.:attr:`tab_width` is a :class:`~kivy.properties.NumericProperty` anddefaults to 4.'''
padding_x

文本的水平填充,格式为[padding_left, padding_right]或[padding_horizontal],默认为[0,0]

 padding_x = VariableListProperty([0, 0], length=2, deprecated=True)'''Horizontal padding of the text: [padding_left, padding_right].padding_x also accepts a one argument form [padding_horizontal].:attr:`padding_x` is a :class:`~kivy.properties.VariableListProperty` anddefaults to [0, 0]. This might be changed by the current theme... deprecated:: 1.7.0Use :attr:`padding` instead.'''
_label_cached
_label_cached

_label_cached是一个缓存的Label对象,用于存储TextInput中已经渲染过的文本标签,以便提高TextInput的渲染性能。

_label_cached是在TextInput的绘制方法中使用的,它存储了当前已经绘制的文本标签,避免每次绘制时都需要重新创建和设置标签的属性,从而提高渲染性能。
_lines
_lines = ListProperty([])

_lines则是用于存储TextInput中文本的行数,可能会根据文本内容的变化而动态调整。

_lines则是在TextInput的文本内容发生变化时更新的,它用于计算文本的行数,以便在padding_y中设置合适的内边距来使文本垂直居中。

个人总结:这简直就是从天方夜谭里抽出根绣花儿针削铁如泥,真是6歪了。

center[0]    
Module: kivy.uix.widget

center[0]指的是TextInput组件的中心点在x轴方向上的位置。padding_x属性设置了TextInput组件中文本的左右边距,使得文本内容相对于组件中心向左偏移了1/6的组件宽度,这个偏移量是通过center来计算的。具体来说,这个计算是通过将center除以6.0得到的值来实现的。

center
Center position of the widget.center is a ReferenceListProperty of (center_x, center_y) properties.

个人总结: 在TextInput里翻了半天,没找到center,这应该是继承它祖父的属性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/20400.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows安装nodeJs,以及常用操作

1. 官网(Node.js — Run JavaScript Everywhere (nodejs.org))下载想要安装的node版本 的安装包完成安装 2.环境变量设置&#xff1a; 系统变量&#xff1a; Path新增&#xff1a;D:\Program Files\nodejs (node安装目录) 3.设置淘宝源&#xff1a; npm config set registr…

传感器和变送器的区别介绍

从它的名称来看&#xff0c;传与感二字。传是指传输&#xff0c;感是指感知。实际上是先有感知&#xff0c;其次转换&#xff0c;最后传输。因此传输是目的&#xff0c;转换是手段&#xff0c;感知是基础。把能够将被测变量&#xff08;温度、压力、液位、流量&#xff09;感知…

Python操作系统交互的15个实用命令

对于Python初学者而言&#xff0c;掌握如何使用Python与操作系统进行交互是一项非常实用的技能。这不仅能够让你的脚本更加灵活强大&#xff0c;还能在自动化任务处理、文件管理等方面大显身手。下面&#xff0c;我们将通过10个简单到复杂的实例&#xff0c;逐步引导你学习如何…

港湾周评|李小加“刀刃向内”裁员

《港湾商业观察》李镭 近年来争议颇大的滴灌通风波不断。 在交100万付费上班不久&#xff0c;最新又被曝出裁员。这位前港交所总裁、金融圈鼎鼎大名的李小加&#xff0c;没想到成立不足三年便迎来了重大挑战。 日前&#xff0c;滴灌通确认了公司组织架构已经调整&#xff0c…

Flutter 中的 SliderTheme 小部件:全面指南

Flutter 中的 SliderTheme 小部件&#xff1a;全面指南 Flutter 是一个创新的 UI 工具包&#xff0c;它允许开发者通过 Dart 语言来创建美观、高效的移动和 Web 应用。在 Flutter 的交互组件中&#xff0c;滑块&#xff08;Slider&#xff09;是一种常用的控件&#xff0c;用于…

【教程】如何实现WordPress网站降级(用于解决插件和主题问题)

在最新可用版本上运行WordPress安装、插件和主题是使用该平台的关键最佳实践。还建议使用最新版本的PHP。但是,在某些情况下,这是不谨慎或不可能的。 如果您发现自己处于这种情况,您可能需要撤消更新并降级您的WordPress网站(或其中的一部分)。幸运的是,有一些方法可用于…

对已运行的kafka集群添加副本

在已经运行的kafka集群中&#xff08;业务主题已创建&#xff09;&#xff0c;如果一开始配置的默认副本是1&#xff0c;则后续再去修改配置文件为3&#xff0c;也只是针对后续新增的主题有效。原先主题不会生效。需要通过json文件去修改。 先创建__consumer_offsets主题的JSO…

深入浅出Java多线程

系列文章目录 文章目录 系列文章目录前言一、多线程基础概念介绍线程的状态转换图线程的调度一些常见问题 二、Java 中线程的常用方法介绍Java语言对线程的支持Thread常用的方法三、线程初体验&#xff08;编码示例&#xff09; 前言 前些天发现了一个巨牛的人工智能学习网站&…

【验证码识别】Yolov8入门到实战点选验证码数据集分类训练,孪生训练,导出onnx,搭建部署接口

【验证码识别】Yolov8入门到实战点选验证码数据集分类训练&#xff0c;孪生训练&#xff0c;导出onnx&#xff0c;搭建部署接口 文章目录 【验证码识别】Yolov8入门到实战点选验证码数据集分类训练&#xff0c;孪生训练&#xff0c;导出onnx&#xff0c;搭建部署接口声明一、标…

scButterfly:单细胞跨模态翻译

技术限制导致了高噪声的多模态数据。尽管已经提出了计算方法来跨模态翻译单细胞数据&#xff0c;但是这些方法的泛化性仍然受到制约。scButterfly是一种基于双重对齐变分自编码器和数据增强方案的多功能单细胞跨模态翻译方法。通过对多个数据集进行全面的实验&#xff0c;证明了…

工业安全智勇较量,赛宁网安工业靶场决胜工业网络攻防对抗新战场

2024年1月30日&#xff0c;工信部发布《工业控制系统网络安全防护指南》&#xff08;工信部网安〔2024〕14号&#xff09;&#xff0c;围绕安全管理、技术防护、安全运营、责任落实四方面提出安全防护要求&#xff0c;强调聚焦安全薄弱关键环节&#xff0c;强化技术应对策略&am…

关于磁盘管理

磁盘管理是操作系统提供的一项功能&#xff0c;用于高效地组织、维护和控制计算机的硬盘驱动器及其卷&#xff08;分区&#xff09;。通过磁盘管理工具&#xff0c;用户和管理员可以执行多种与存储相关的高级任务&#xff0c;主要包括&#xff1a; 初始化新磁盘&#xff1a; …

js前端怎么封装

js前端怎么封装 在JavaScript前端开发中&#xff0c;封装是一个至关重要的概念。通过封装&#xff0c;我们可以将复杂的逻辑和功能隐藏在简单的接口后面&#xff0c;提高代码的可读性、可维护性和复用性。那么&#xff0c;如何有效地在JavaScript前端进行封装呢&#xff1f;接…

Passion编程语言:探索其深邃的四个维度、五大特性、六大应用及七大前景

Passion编程语言&#xff1a;探索其深邃的四个维度、五大特性、六大应用及七大前景 在编程的浩瀚宇宙中&#xff0c;Passion编程语言如同一颗璀璨的新星&#xff0c;以其独特的魅力和深厚的内涵吸引着越来越多的探索者。本文将从四个方面、五个方面、六个方面和七个方面&#…

Caliburn.Micro框架学习笔记——窗体管理对象的基本使用

Caliburn.Micro 的窗口管理器&#xff08;Window Manager&#xff09;是一个强大的工具&#xff0c;用于管理 WPF 应用程序中的窗口和对话框。它简化了窗口的创建、显示和关闭等操作&#xff0c;使得视图和视图模型的交互更加直观和易于维护。 它能够实现的功能包括 显示窗口&…

大学生社团活动平台系统基于springboot+vue的社团管理系统java项目sprignboot项目

文章目录 大学生社团活动平台一、项目介绍二、部分功能截图三、部分代码展示四、底部获取项目源码&#xff08;9.9&#xffe5;带走&#xff09; 大学生社团活动平台 一、项目介绍 基于springbootvue的前后端分离大学生社团活动平台 系统角色 : 学生、社长、管理员 1、学生…

pytorch笔记:topk

1 介绍 返回给定输入张量沿指定维度的最大的 k 个元素 如果没有指定 dim 参数&#xff0c;则默认选择输入张量的最后一个维度如果 largest 设置为 False&#xff0c;则返回最小的 k 个元素。返回一个命名元组 (values, indices)&#xff0c;其中包含每一行在指定维度 dim 上最…

Frida

配置环境 查看ubuntu版本 cat /etc/lsb-release设置时区 dpkg-reconfigure tzdataadb adb&#xff1a;&#xff08;Android Debug Bridge&#xff09;是一个用于在计算机和Android设备之间进行通信的命令行工具。它允许开发人员在计算机上执行各种命令&#xff0c;如安装应…

Git配置SSH-Key

git config --global user.name 沈健 git config --global user.email sjshenjianoutlook.com初次使用 SSH 协议进行代码克隆、推送等操作时&#xff0c;需按下述提示完成 SSH 配置 1 生成 RSA 密钥 ssh-keygen -t rsa2. 获取 RSA 公钥内容&#xff0c;并配置到 SSH公钥 中 …

FENDI CLUB精酿啤酒中原麦汁浓度的高低有何区别?

关于精酿啤酒&#xff0c;有两个关键数据&#xff0c;一个是原麦汁浓度&#xff0c;一个是酒精度。酒精度无非是含酒精的高低&#xff0c;但原麦汁浓度又是什么呢&#xff1f;另外精酿啤酒中原麦汁浓度有高有低&#xff0c;究竟有哪些区别呢&#xff1f; 原麦汁浓度是指啤…