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`ccu.fancyplots._gui.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