Shofel2_T124_python/venv/lib/python3.10/site-packages/asciimatics/widgets/text.py

191 lines
8.0 KiB
Python

# -*- coding: utf-8 -*-
"""This widget implements a text based input field"""
from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
from builtins import chr
from re import match
from asciimatics.event import KeyboardEvent, MouseEvent
from asciimatics.screen import Screen
from asciimatics.widgets.utilities import _find_min_start, _enforce_width, _get_offset
from asciimatics.widgets.widget import Widget
class Text(Widget):
"""
A Text widget is a single line input field.
It consists of an optional label and an entry box.
"""
__slots__ = ["_label", "_column", "_start_column", "_on_change", "_validator", "_hide_char",
"_max_length", "_readonly"]
def __init__(self, label=None, name=None, on_change=None, validator=None, hide_char=None,
max_length=None, readonly=False, **kwargs):
"""
:param label: An optional label for the widget.
:param name: The name for the widget.
:param on_change: Optional function to call when text changes.
:param validator: Optional definition of valid data for this widget.
This can be a function (which takes the current value and returns True for valid
content) or a regex string (which must match the entire allowed value).
:param hide_char: Character to use instead of what the user types - e.g. to hide passwords.
:param max_length: Optional maximum length of the field. If set, the widget will limit
data entry to this length.
:param readonly: Whether the widget prevents user input to change values. Default is False.
Also see the common keyword arguments in :py:obj:`.Widget`.
"""
super(Text, self).__init__(name, **kwargs)
self._label = label
self._column = 0
self._start_column = 0
self._on_change = on_change
self._validator = validator
self._hide_char = hide_char
self._max_length = max_length
self._readonly = readonly
def set_layout(self, x, y, offset, w, h):
# Do the usual layout work. then apply max length to resulting dimensions.
super(Text, self).set_layout(x, y, offset, w, h)
if self._max_length:
# Allow extra char for cursor, so contents don't scroll at required length
self._w = min(self._w, self._max_length + self._offset + 1)
def update(self, frame_no):
self._draw_label()
# Calculate new visible limits if needed.
self._start_column = min(self._start_column, self._column)
self._start_column += _find_min_start(self._value[self._start_column:self._column + 1],
self.width, self._frame.canvas.unicode_aware,
self._column >= self.string_len(self._value))
# Render visible portion of the text.
(colour, attr, background) = self._pick_colours("readonly" if self._readonly else "edit_text")
text = self._value[self._start_column:]
text = _enforce_width(text, self.width, self._frame.canvas.unicode_aware)
if self._hide_char:
text = self._hide_char[0] * len(text)
text += " " * (self.width - self.string_len(text))
self._frame.canvas.print_at(
text,
self._x + self._offset,
self._y,
colour, attr, background)
# Since we switch off the standard cursor, we need to emulate our own
# if we have the input focus.
if self._has_focus:
text_width = self.string_len(text[:self._column - self._start_column])
self._draw_cursor(
" " if self._column >= len(self._value) else self._hide_char[0] if self._hide_char
else self._value[self._column],
frame_no,
self._x + self._offset + text_width,
self._y)
def reset(self):
# Reset to original data and move to end of the text.
self._start_column = 0
self._column = len(self._value)
def process_event(self, event):
if isinstance(event, KeyboardEvent):
if event.key_code == Screen.KEY_BACK and not self._readonly:
if self._column > 0:
# Delete character in front of cursor.
self._set_and_check_value("".join([self._value[:self._column - 1],
self._value[self._column:]]))
self._column -= 1
elif event.key_code == Screen.KEY_DELETE and not self._readonly:
if self._column < len(self._value):
self._set_and_check_value("".join([self._value[:self._column],
self._value[self._column + 1:]]))
elif event.key_code == Screen.KEY_LEFT:
self._column -= 1
self._column = max(self._column, 0)
elif event.key_code == Screen.KEY_RIGHT:
self._column += 1
self._column = min(len(self._value), self._column)
elif event.key_code == Screen.KEY_HOME:
self._column = 0
elif event.key_code == Screen.KEY_END:
self._column = len(self._value)
elif event.key_code >= 32 and not self._readonly:
# Enforce required max length - swallow event if not allowed
if self._max_length is None or len(self._value) < self._max_length:
# Insert any visible text at the current cursor position.
self._set_and_check_value(chr(event.key_code).join([self._value[:self._column],
self._value[self._column:]]))
self._column += 1
else:
# Ignore any other key press.
return event
elif isinstance(event, MouseEvent):
# Mouse event - rebase coordinates to Frame context.
if event.buttons != 0:
if self.is_mouse_over(event, include_label=False):
self._column = (self._start_column +
_get_offset(self._value[self._start_column:],
event.x - self._x - self._offset,
self._frame.canvas.unicode_aware))
self._column = min(len(self._value), self._column)
self._column = max(0, self._column)
return None
# Ignore other mouse events.
return event
else:
# Ignore other events
return event
# If we got here, we processed the event - swallow it.
return None
def required_height(self, offset, width):
return 1
@property
def frame_update_count(self):
# Force refresh for cursor if needed.
return 5 if self._has_focus and not self._frame.reduce_cpu else 0
@property
def readonly(self):
"""
Whether this widget is readonly or not.
"""
return self._readonly
@readonly.setter
def readonly(self, new_value):
self._readonly = new_value
@property
def value(self):
"""
The current value for this Text.
"""
return self._value
@value.setter
def value(self, new_value):
self._set_and_check_value(new_value, reset=True)
def _set_and_check_value(self, new_value, reset=False):
# Only trigger the notification after we've changed the value.
old_value = self._value
self._value = new_value if new_value else ""
if reset:
self.reset()
if old_value != self._value and self._on_change:
self._on_change()
if self._validator:
if callable(self._validator):
self._is_valid = self._validator(self._value)
else:
self._is_valid = match(self._validator, self._value) is not None