Source code for mytk.entries

import re
import tkinter.ttk as ttk
from tkinter import IntVar, StringVar

from .base import Base
from .labels import Label
from .views import View


[docs] class Entry(Base): """A single-line text entry widget wrapping ttk.Entry.""" def __init__( self, *args, value="", character_width=None, **kwargs, ): super().__init__(*args, **kwargs) self._widget_args["width"] = character_width self.value = value self.value_variable = StringVar(value=value) self.bind_properties("value", self, "value_variable")
[docs] def create_widget(self, master): """Create the ttk.Entry widget and bind its text variable.""" self.parent = master self.widget = ttk.Entry(master, width=self.character_width) self.bind_textvariable(self.value_variable) self.widget.bind("<Return>", self.event_return_callback) self.widget.update()
@property def character_width(self): """Get or set the entry width in characters.""" if self.widget is None: return self._widget_args["width"] else: return self.widget["width"] @character_width.setter def character_width(self, value): if self.widget is None: self._widget_args["width"] = value else: self.widget["width"] = value @property def width(self): """Get the entry width in pixels. Raises: NotImplementedError: If the widget has not been placed yet. """ if self.widget is None: raise NotImplementedError( "It is not possible to get the width in pixels for an Entry before placing it into a window." ) else: self.widget.update() return self.widget.winfo_width() @width.setter def width(self, pixel_width): raise NotImplementedError( "It is not possible (yet) to set the width in pixels for an Entry." )
[docs] def event_return_callback(self, event): """Move focus to the parent widget when Return is pressed.""" self.parent.focus_set()
[docs] class FormattedEntry(Base): """A text entry that formats its numeric value using a format string and regex.""" def __init__( self, *args, value=0, character_width=None, format_string=None, reverse_regex=None, **kwargs, ): super().__init__( *args, **kwargs, ) self.format_string = format_string if self.format_string is None: self.format_string = r"{0}" self.reverse_regex = reverse_regex if self.reverse_regex is None: self.reverse_regex = r"(.+)" self._value = value self.value_variable = StringVar(value=self.format_string.format(value)) self._widget_args["width"] = character_width @property def value(self): """Get or set the numeric value, applying the format string on change.""" return self._value @value.setter def value(self, new_value): try: new_value = float(new_value) except (ValueError, TypeError): return if new_value != self._value: self._value = new_value self.value_variable.set( value=self.format_string.format(self._value) ) @property def character_width(self): """Get or set the entry width in characters.""" if self.widget is None: return self._widget_args["width"] else: return self.widget["width"] @character_width.setter def character_width(self, value): if self.widget is None: self._widget_args["width"] = value else: self.widget["width"] = value
[docs] def create_widget(self, master): """Create the ttk.Entry widget with format and focus bindings.""" self.parent = master self.widget = ttk.Entry(master, width=self.character_width) self.bind_textvariable(self.value_variable) self.widget.bind("<Return>", self.event_return_callback) self.widget.bind("<FocusOut>", self.event_focus_out) self.widget.update()
[docs] def event_return_callback(self, event): """Move focus to the parent widget when Return is pressed.""" self.parent.focus_set()
[docs] def event_focus_out(self, event): """Parse the displayed text back into a numeric value on focus loss.""" match = re.search(self.reverse_regex, self.value_variable.get()) if match is not None and match.group(1) is not None: try: self.value = float(match.group(1)) except ValueError: self.value = 0 else: self.value = 0 self.value_variable.set(value=self.format_string.format(self.value))
[docs] class CellEntry(Base): """An inline entry widget for editing a single cell in a TableView.""" def __init__( self, *args, tableview, item_id, column_name, user_event_callback=None, **kwargs, ): super().__init__(*args, **kwargs) self.tableview = tableview self.item_id = item_id self.column_name = column_name self.column_id = self.tableview.columns.index(self.column_name) self.value_type = str # default field_properties = self.tableview.data_source.get_field_properties( self.column_name ) if field_properties is not None: self.value_type = field_properties.get("type", str) self.user_event_callback = user_event_callback
[docs] def create_widget(self, master): """Create the ttk.Entry widget pre-filled with the current cell value.""" record = self.tableview.data_source.record(self.item_id) if self.value_type is not str: selected_text = f"{(record[self.column_name]):g}" else: selected_text = str(record[self.column_name]) self.parent = master self.value_variable = StringVar() self.widget = ttk.Entry(master, textvariable=self.value_variable) self.widget.bind("<FocusOut>", self.event_focusout_callback) self.widget.bind("<Return>", self.event_return_callback) self.widget.insert(0, selected_text)
[docs] def event_return_callback(self, event): """Commit the edited value back to the data source on Return.""" record = dict(self.tableview.data_source.record(self.item_id)) try: record[self.column_name] = self.value_type( self.value_variable.get() ) except ValueError: record[self.column_name] = None self.tableview.item_modified( item_id=self.item_id, modified_record=record ) self.event_generate("<FocusOut>")
[docs] def event_focusout_callback(self, event): """Invoke the user callback and destroy the entry widget on focus out.""" if self.user_event_callback is not None: self.user_event_callback(event, self) self.widget.destroy()
[docs] class IntEntry(Base): """An integer spinbox entry with configurable minimum, maximum, and increment.""" def __init__( self, *args, value=0, width=None, minimum=0, maximum=100, increment=1, **kwargs, ): super().__init__(*args, **kwargs) self._widget_args = { "width": width, "from": minimum, "to": maximum, "increment": increment, } self.value_variable = IntVar(value=value)
[docs] def create_widget(self, master): """Create the ttk.Spinbox widget and bind its text variable.""" self.parent = master self.widget = ttk.Spinbox(master, **self._widget_args) self.bind_textvariable(self.value_variable)
@property def value(self): """Get or set the integer value, clamped to the configured range.""" return self.value_variable.get() @value.setter def value(self, value): if value > self.maximum: self.value_variable.set(value=self.maximum) elif value < self.minimum: self.value_variable.set(value=self.minimum) else: self.value_variable.set(value=value) @property def minimum(self): """Get or set the minimum allowed value.""" if self.widget is None: return self._widget_args.get("from") else: return self.widget["from"] @minimum.setter def minimum(self, value): if self.widget is None: self._widget_args["from"] = value else: self.widget["from"] = value @property def maximum(self): """Get or set the maximum allowed value.""" if self.widget is None: return self._widget_args.get("to") else: return self.widget["to"] @maximum.setter def maximum(self, value): if self.widget is None: self._widget_args["to"] = value else: self.widget["to"] = value @property def increment(self): """Get or set the step increment for the spinbox.""" if self.widget is None: return self._widget_args.get("increment") else: return self.widget["increment"] @increment.setter def increment(self, value): if self.widget is None: self._widget_args["increment"] = value else: self.widget["increment"] = value
NumericEntry = IntEntry # backward-compatible alias
[docs] class LabelledEntry(View): """A composite widget combining a Label and an Entry side by side.""" def __init__(self, *args, label, text="", character_width=None, **kwargs): super().__init__(*args, width=200, height=25, **kwargs) self.entry = Entry(value=text, character_width=character_width) self.label = Label(text=label)
[docs] def create_widget(self, master): """Create the label and entry widgets arranged in a single row.""" super().create_widget(master) self.all_resize_weight(1) self.label.grid_into(self, row=0, column=0, padx=5) self.entry.grid_into(self, row=0, column=1, padx=5) self.value_variable = self.entry.value_variable