Source code for pygvisuals.widgets.selection_text_widget

# --- imports
# pygame imports
import pygame

# local imports
from .text_widget import TextWidget
from ..designs import getDefaultDesign, getFallbackDesign
from ..util import inherit_docstrings_from_superclass


# constants
START = 0
"""A index-constant for the first index of a widget's content."""
END = 'e'
"""A index-constant for the last index of a widget's content."""
CURSOR = 'c'
"""A index-constant for the cursor of a widget."""
INSERT = CURSOR
"""A index-constant alias for CURSOR."""
CURRENT = CURSOR
"""A index-constant alias for CURSOR."""
SELECTION = 's'
"""A index-constant for the selection-index of a widget."""

# set defaults
getFallbackDesign().selection_overlay = (45, 110, 235, 120)
"""Color to be overlayed by default for content that has been selected in a widget."""


[docs]class SelectionTextWidget(TextWidget): """ Underlying class for widgets using selectable content. """ def __init__(self, x, y, width, height, text="", font=getDefaultDesign().font, editable=False, validation_function=(lambda *x: True), selection_overlay=getDefaultDesign().selection_overlay): """ Initialisation of a SelectionTextWidget. Args: inherit_doc:: arguments editable: A boolean indicating whether the widget's content is editable by the user. The default value is False, meaning it can not be edited by user-input. validation_function: A function that validates changed content. It will receive three arguments (the new content, the old content and the widget-object) and should return a boolean indicating whether the change is valid (True when valid). The old content can be None if it was not set before; the new content can be anything that is being passed to setText(). The default value is a function that accepts every change. selection_overlay: A color-like object that can be interpreted as a color by pygame (such as a tuple with RGB values); this is used as an overlay for content that has been selected. The default value is the global default for the selection-color. """ self.validation_function = validation_function super(SelectionTextWidget, self).__init__(x, y, width, height, text, font) self._cursor = 0 self._selection_index = 0 self.editable = editable self.selection_overlay = selection_overlay
[docs] def setEditable(self, editable): """ Set whether the widget's content is editable by the user (via user-input). Args: editable: A boolean indicating whether the widget's content is editable by the user. Returns: Itsself (the widget) for convenience. """ self._editable = bool(editable) return self
[docs] def isEditable(self): """ Return whether the widget's content is editable by the user (via user-input). Returns: A boolean indicating whether the widget's content is editable by the user. """ return self._editable
[docs] def setValidation(self, validation_function): """ Set the widget's validation-function. Args: validation_function: A function that validates changed content. It will receive three arguments (the new content, the old content and the widget-object) and should return a boolean indicating whether the change is valid (True when valid). The old content can be None if it was not set before; the new content can be anything that is being passed to setText(). Returns: Itsself (the widget) for convenience. """ self._validation_function = validation_function return self
[docs] def getValidation(self): """ Return the widget's validation-function. Returns: A function that validates changed content. It will receive three arguments (the new content, the old content and the widget-object) and should return a boolean indicating whether the change is valid (True when valid). The old content can be None if it was not set before; the new content can be anything that is being passed to setText(). """ return self._validation_function
[docs] def setText(self, text, return_success_boolean=False): """ Additionally validate the change of content. inherit_doc:: """ if self.validation_function and callable(self.validation_function) and self.validation_function(text, getattr(self, "text", None), self): super(SelectionTextWidget, self).setText(text) if return_success_boolean: return True if return_success_boolean: return False return self
[docs] def insert(self, index, text): """ Insert a given text at the given index. Args: index: An integer (or known constant) representing the position the text should be insterted at. text: A string specifing the content to add to the content of the widget. Returns: A boolean indicating whether the change was successful. """ index = self.getActualIndex(index) return self.setText(self.text[:index] + text + self.text[index:], True)
[docs] def delete(self, start, end): """ Deletes the widget's content between the two given indices. Args: start: An integer representing the index from which the content should be deleted. end: An integer representing the index until which the content should be deleted. Returns: A boolean indicating whether the change was successful. """ start, end = self._sort(start, end) return self.setText(self.text[:start] + self.text[end:], True)
[docs] def setSelectionOverlay(self, color): """ Set the widget's color to overlay for content that has been selected. Args: color: A color-like object that can be interpreted as a color by pygame (such as a tuple with RGB values). Returns: Itsself (the widget) for convenience. """ self._selection_overlay = color self.markDirty() return self
[docs] def getSelectionOverlay(self): """ Return the widget's color to overlay for content that has been selected. Returns: A color-like object that represents the widget's color to overlay for content that has been selected. """ return self._selection_overlay
[docs] def setCursor(self, index): """ Set the widget's cursor-position. Args: index: An integer (or known constant) representing the index the cursor should be set to. Returns: Itsself (the widget) for convenience. """ return self.setSelection(index, index)
[docs] def moveCursor(self, amount): """ Move the widget's cursor-position by the given amount. Args: amount: An integer representing the amount the cursor should be moved by. Returns: Itsself (the widget) for convenience. """ return self.setCursor(self.cursor + int(amount))
[docs] def getCursor(self): """ Return the widget's cursor-position. Returns: An integer representing the index the cursor is at. """ return self._cursor
[docs] def setSelectionIndex(self, index): """ Set the widget' selection-index. Args: index: An integer (or known constant) representing the index the selection-index should be set to. Returns: Itsself (the widget) for convenience. """ return self.setSelection(index, CURSOR)
[docs] def moveSelectionIndex(self, amount): """ Move the widget's cursor-position by the given amount. Args: amount: An integer representing the amount the cursor should be moved by. Returns: Itsself (the widget) for convenience. """ return self.setSelectionIndex(self.selection_index + int(amount))
[docs] def getSelectionIndex(self): """ Return the widget' selection-index. Returns: An integer representing the index the selection-index is at. """ return self._selection_index
[docs] def setSelection(self, selection_index, cursor): """ Set the widget' selection between the given bounds. Args: selection_index: An integer (or known constant) representing the index the selection should start. This will be the position of the selection-index. cursor: An integer (or known constant) representing the index the selection should end. This will be the position of the cursor. The indices can actually be reversed (meaning the start-index is larger than the end-index) so that the cursor is at the start of the selection. Returns: Itsself (the widget) for convenience. """ self._selection_index = self.getActualIndex(selection_index) self._cursor = self.getActualIndex(cursor) self.markDirty() return self
[docs] def getSelection(self): """ Return the widget' selection-range. Returns: A tuple (start, end) with the start- and end-index of the selection. This is not the selected content, only the indices of the range! """ return self._sort(self.selection_index, self.cursor)
[docs] def getActualIndex(self, index, constrain=True): """ Return the actual index corresponding to the given representation. This converts known constants (e.g. END, CURSOR) to the corresponding integers. Args: index: An integer (or known constant) to be converted. constrain: A boolean indicating whether the given index should be constrained to valid indices for the content or not. The default value is True, meaning that the returned index is constrained. Returns: An integer representing the actual index the given value corresponds to. """ if index == CURSOR: return self.cursor if index == END: return len(self.text) if index == SELECTION: return self.selection_index if constrain: return min(max(int(index), START), self.getActualIndex(END)) return index
def _indexToPos(self, index): """ Return the relative coordinate (x, y) corresponding to the given index. This is an internal function. Args: index: An integer (or known constant) to be converted. Returns: A pair of integers (x, y) representing a relative coordinate. """ return self.font.size(self.text[:self.getActualIndex(index)])[0], 0 def _posToIndex(self, x, y): """ Return the index corresponding to the given relative coordinate (x, y). This is an internal function. Args: x: An integer representing a relative x-coordinate. y: An integer representing a relative y-coordinate. Returns: An integer representing the index corresponding to the given relative coordinate. """ length = len(self.text) x = min(float(x), (self.font.size(self.text[:-1])[0] + self.font.size(self.text[-1:])[0] * 1.5)) index = 0 n = 0 if self.text: for n in range(max(min(int(x / (self.font.size(self.text)[0] / length)), length - 1), 0), 0, -1): if self.font.size(self.text[:n])[0] + self.font.size(self.text[n])[0] < x: break for index in range(n, length): if self.font.size(self.text[:index])[0] + (self.font.size(self.text[index])[0] * 1.5) > x: break else: index += 1 return index def _sort(self, i, j, constrain=True): """ Return the indices in ascending order. This is an internal function. Args: i: An integer (or known constant) to sort. j: Another integer (or known constant) to sort. constrain: A boolean indicating whether the given indices should be constrained to valid indices for the content or not. The default value is True, meaning that the returned index is constrained. Returns: A tuple (min, max) of the numbers which have been sorted. """ i = self.getActualIndex(i, constrain) j = self.getActualIndex(j, constrain) if i > j: return j, i return i, j
[docs] def update(self, *args): """ Additionally handles the selection and deletion of content. inherit_doc:: """ if len(args) > 0 and self.isActive() and self.isFocused(): event = args[0] if event.type == pygame.KEYDOWN and self.isEditable(): if event.key in (pygame.K_BACKSPACE, pygame.K_DELETE): if self.selection_index == self.cursor: if event.key == pygame.K_DELETE: self.delete(self.selection_index + 1, CURSOR) else: if self.delete(self.selection_index - 1, CURSOR): self.moveCursor(-1) else: s, c = self._sort(SELECTION, CURSOR) if self.delete(s, c): self.setCursor(s) elif event.type == pygame.MOUSEMOTION: if (event.buttons[0] or event.buttons[2]) and self.rect.collidepoint(event.pos): self.setSelection(SELECTION, self._posToIndex(event.pos[0] - self.rect.x, event.pos[1] - self.rect.y)) elif event.type == pygame.MOUSEBUTTONDOWN: if event.button in (1, 3) and self.rect.collidepoint(event.pos): self.setCursor(self._posToIndex(event.pos[0] - self.rect.x, event.pos[1] - self.rect.y)) super(SelectionTextWidget, self).update(*args)
editable = property(lambda obj: obj.isEditable(), lambda obj, arg: obj.setEditable(arg), doc="""The widget' status as a boolean whether its content is editable by the user.""") validation_function = property(lambda obj: obj.getValidation(), lambda obj, arg: obj.setValidation(arg), doc="""The widget's function used for validating change of its content.""") selection_overlay = property(lambda obj: obj.getSelectionOverlay(), lambda obj, arg: obj.setSelectionOverlay(arg), doc="""The widget's color to overlay for content that has been selected.""") selection_index = property(lambda obj: obj.getSelectionIndex(), lambda obj, arg: obj.setSelectionIndex(arg), doc="""The widget's index representing an endpoint for the range of selected content.""") cursor = property(lambda obj: obj.getCursor(), lambda obj, arg: obj.setCursor(arg), doc="""The widget's position of the cursor as a index. This is another endpoint for the range of selected content.""") selection = property(lambda obj: obj.getSelection(), lambda obj, tuple: obj.setSelection(*tuple), doc="""The widget's indices spanning the range of selected content.""")
# inherit docs from superclass SelectionTextWidget = inherit_docstrings_from_superclass(SelectionTextWidget)