159 lines
4.8 KiB
Python
159 lines
4.8 KiB
Python
"""
|
|
|
|
The base class for all messages (including events).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, ClassVar
|
|
|
|
import rich.repr
|
|
from typing_extensions import Self
|
|
|
|
from textual import _time
|
|
from textual._context import active_message_pump
|
|
from textual.case import camel_to_snake
|
|
|
|
if TYPE_CHECKING:
|
|
from textual.dom import DOMNode
|
|
from textual.message_pump import MessagePump
|
|
|
|
|
|
@rich.repr.auto
|
|
class Message:
|
|
"""Base class for a message."""
|
|
|
|
__slots__ = [
|
|
"_sender",
|
|
"time",
|
|
"_forwarded",
|
|
"_no_default_action",
|
|
"_stop_propagation",
|
|
"_prevent",
|
|
]
|
|
|
|
ALLOW_SELECTOR_MATCH: ClassVar[set[str]] = set()
|
|
"""Additional attributes that can be used with the [`on` decorator][textual.on].
|
|
|
|
These attributes must be widgets.
|
|
"""
|
|
bubble: ClassVar[bool] = True # Message will bubble to parent
|
|
verbose: ClassVar[bool] = False # Message is verbose
|
|
no_dispatch: ClassVar[bool] = False # Message may not be handled by client code
|
|
namespace: ClassVar[str] = "" # Namespace to disambiguate messages
|
|
handler_name: ClassVar[str]
|
|
"""Name of the default message handler."""
|
|
|
|
def __init__(self) -> None:
|
|
self.__post_init__()
|
|
|
|
def __post_init__(self) -> None:
|
|
"""Allow dataclasses to initialize the object."""
|
|
self._sender: MessagePump | None = active_message_pump.get(None)
|
|
self.time: float = _time.get_time()
|
|
self._forwarded = False
|
|
self._no_default_action = False
|
|
self._stop_propagation = False
|
|
self._prevent: set[type[Message]] = set()
|
|
|
|
def __rich_repr__(self) -> rich.repr.Result:
|
|
yield from ()
|
|
|
|
def __init_subclass__(
|
|
cls,
|
|
bubble: bool | None = True,
|
|
verbose: bool = False,
|
|
no_dispatch: bool | None = False,
|
|
namespace: str | None = None,
|
|
) -> None:
|
|
super().__init_subclass__()
|
|
if bubble is not None:
|
|
cls.bubble = bubble
|
|
cls.verbose = verbose
|
|
if no_dispatch is not None:
|
|
cls.no_dispatch = no_dispatch
|
|
if namespace is not None:
|
|
cls.namespace = namespace
|
|
name = f"{namespace}_{camel_to_snake(cls.__name__)}"
|
|
else:
|
|
# a class defined inside of a function will have a qualified name like func.<locals>.Class,
|
|
# so make sure we only use the actual class name(s)
|
|
qualname = cls.__qualname__.rsplit("<locals>.", 1)[-1]
|
|
# only keep the last two parts of the qualified name of deeply nested classes
|
|
# for backwards compatibility, e.g. A.B.C.D becomes C.D
|
|
namespace = qualname.rsplit(".", 2)[-2:]
|
|
name = "_".join(camel_to_snake(part) for part in namespace)
|
|
cls.handler_name = f"on_{name}"
|
|
|
|
@property
|
|
def control(self) -> DOMNode | None:
|
|
"""The widget associated with this message, or None by default."""
|
|
return None
|
|
|
|
@property
|
|
def is_forwarded(self) -> bool:
|
|
"""Has the message been forwarded?"""
|
|
return self._forwarded
|
|
|
|
def _set_forwarded(self) -> None:
|
|
"""Mark this event as being forwarded."""
|
|
self._forwarded = True
|
|
|
|
def set_sender(self, sender: MessagePump) -> Self:
|
|
"""Set the sender of the message.
|
|
|
|
Args:
|
|
sender: The sender.
|
|
|
|
Note:
|
|
When creating a message the sender is automatically set.
|
|
Normally there will be no need for this method to be called.
|
|
This method will be used when strict control is required over
|
|
the sender of a message.
|
|
|
|
Returns:
|
|
Self.
|
|
"""
|
|
self._sender = sender
|
|
return self
|
|
|
|
def can_replace(self, message: "Message") -> bool:
|
|
"""Check if another message may supersede this one.
|
|
|
|
Args:
|
|
message: Another message.
|
|
|
|
Returns:
|
|
True if this message may replace the given message
|
|
"""
|
|
return False
|
|
|
|
def prevent_default(self, prevent: bool = True) -> Message:
|
|
"""Suppress the default action(s). This will prevent handlers in any base classes
|
|
from being called.
|
|
|
|
Args:
|
|
prevent: True if the default action should be suppressed,
|
|
or False if the default actions should be performed.
|
|
"""
|
|
self._no_default_action = prevent
|
|
return self
|
|
|
|
def stop(self, stop: bool = True) -> Message:
|
|
"""Stop propagation of the message to parent.
|
|
|
|
Args:
|
|
stop: The stop flag.
|
|
"""
|
|
self._stop_propagation = stop
|
|
return self
|
|
|
|
def _bubble_to(self, widget: MessagePump) -> None:
|
|
"""Bubble to a widget (typically the parent).
|
|
|
|
Args:
|
|
widget: Target of bubble.
|
|
"""
|
|
self._no_default_action = False
|
|
widget.post_message(self)
|