Source code for ccu.fancyplots.gui.frames

"""Validation-enabled GUI elements.

This module defines the :class:`FancyFormatFrame` class.
"""

import tkinter as tk
from tkinter import ttk
from typing import Any
from typing import Protocol
from typing import TypeVar

from ccu.fancyplots.gui.tooltip import Tooltip
from ccu.fancyplots.validation import Serializer
from ccu.fancyplots.validation import Validator
from ccu.fancyplots.validation import default_serializer
from ccu.fancyplots.validation import highlight_and_warn
from ccu.fancyplots.validation import no_validation_validator

_T = TypeVar("_T")


[docs] class UpdatableFrame(Protocol): """A GUI element whose children's state depend on their data."""
[docs] def update_frames(self) -> None: """This function should update all children of the frame."""
[docs] class FancyFormatFrame(ttk.LabelFrame): """A ``ttk.LabelFrame`` for setting formatting parameters. Note that :class:`FancyFormatFrame` instances emit the <<Validate>> event. To listen for this event, handlers should be bound to the event using ``Misc.bind`` and ``Misc.event_add``. Attributes: entry: A :class:`ttk.Entry` in which a user specifies a formatting parameter. tooltip: A :class`.tooltip.Tooltip` displaying help text. validator: A :class:`.validation.Validator` used to validate and convert the text in ``entry``. serializer: A :class:`.validation.Serializer` used to convert the Python value to a string. """ def __init__( self, parent: ttk.LabelFrame, *args, label: str = "", value: Any = None, tooltip: str = "", validator: Validator[_T] = no_validation_validator, serializer: Serializer[_T] = default_serializer, pad: tuple[float, float, float, float] = (5, 3, 5, 3), **kwargs, ) -> None: """A `ttk.LabelFrame` for setting formatting parameters. Args: parent: The containing :class:`.ttk.LabelFrame`. *args: Positional parameters for :class:`.ttk.LabelFrame`. label: The name of the formatting parameter. Defaults to "". value: The initial value of the formatting parameter. Defaults to None. tooltip: The tooltip message. Defaults to "". validator: A :class:`Validator` for validating and converting user input. Defaults to :func:`.validation.no_validation_validator`. serializer: A :class:`validation.Serializer` instance for converting the Python version of the value to a string. pad: Specify the padding `(left, top, bottom, right)` to use for the tooltip. Defaults to `(5, 3, 5, 3)`. **kwargs: Keyword arguments for :class:`.ttk.LabelFrame`. """ super().__init__(parent, *args, text=label, **kwargs) self.parent = parent self._var = tk.StringVar(value="") self.entry = ttk.Entry( self, validate="focusout", validatecommand=(self.register(self.alerting_validator), "%P"), invalidcommand=self.register(highlight_and_warn), textvariable=self._var, ) self.tooltip = Tooltip(self, text=tooltip) self.entry.grid( padx=(pad[0], pad[2]), pady=(pad[1], pad[3]), sticky=tk.NSEW, ) self.grid() self.validator = validator self.serializer = serializer self.value = value @property def label_text(self) -> str: """The text used for the `FancyFormatFrame` label.""" return self.cget("text") @property def python_value(self) -> _T: """The Python value of the text in :attr:`FancyFormatFrame.entry`. Raises: ValidationError: The current value in :attr:`FancyFormatFrame.entry` is invalid. """ return self.validator(self._var.get(), validate_only=False) @property def value(self): """The text in :attr:`FancyFormatFrame.entry`.""" return self._var.get() @value.setter def value(self, new_value: Any): self._var.set(self.serializer(new_value))
[docs] def alerting_validator(self, value: str) -> bool: """Validate a value and generate a ``<<Validate>>`` event. Args: value: The value to validate. Returns: True if the value if valid. False, otherwise. Note: Listeners should be bound to the ``<<Validate>>`` event in order to respond to this behaviour. """ valid = self.validator(value) if valid: self.parent.event_generate("<<Validate>>", when="tail") return valid