CmdForge/.venv/lib/python3.12/site-packages/urwid/widget/container.py

182 lines
5.7 KiB
Python

from __future__ import annotations
import abc
import enum
import typing
from .constants import Sizing, WHSettings
if typing.TYPE_CHECKING:
from collections.abc import Iterable, Iterator
from .widget import Widget
# Ideally, we would like to use an IntFlag coupled with enum.auto().
# However, doing many bitwise operations (which happens when nesting too many
# widgets ...) on IntFlag is orders of magnitude slower than doing the same
# operations on IntEnum.
class _ContainerElementSizingFlag(enum.IntEnum):
# fmt: off
NONE = 0b000000
BOX = 0b000001
FLOW = 0b000010
FIXED = 0b000100
WH_WEIGHT = 0b001000
WH_PACK = 0b010000
WH_GIVEN = 0b100000
# fmt: on
@staticmethod
def reverse_flag(bitfield: int) -> tuple[frozenset[Sizing], WHSettings | None]:
"""Get flag in public API format."""
sizing: set[Sizing] = set()
if bitfield & _ContainerElementSizingFlag.BOX:
sizing.add(Sizing.BOX)
if bitfield & _ContainerElementSizingFlag.FLOW:
sizing.add(Sizing.FLOW)
if bitfield & _ContainerElementSizingFlag.FIXED:
sizing.add(Sizing.FIXED)
if bitfield & _ContainerElementSizingFlag.WH_WEIGHT:
return frozenset(sizing), WHSettings.WEIGHT
if bitfield & _ContainerElementSizingFlag.WH_PACK:
return frozenset(sizing), WHSettings.PACK
if bitfield & _ContainerElementSizingFlag.WH_GIVEN:
return frozenset(sizing), WHSettings.GIVEN
return frozenset(sizing), None
@staticmethod
def log_string(bitfield: int) -> str:
"""Get desctiprion in public API format."""
sizing, render = _ContainerElementSizingFlag.reverse_flag(bitfield)
render_string = f" {render.upper()}" if render else ""
return "|".join(sorted(mode.upper() for mode in sizing)) + render_string
class WidgetContainerMixin:
"""
Mixin class for widget containers implementing common container methods
"""
def __getitem__(self, position) -> Widget:
"""
Container short-cut for self.contents[position][0].base_widget
which means "give me the child widget at position without any
widget decorations".
This allows for concise traversal of nested container widgets
such as:
my_widget[position0][position1][position2] ...
"""
return self.contents[position][0].base_widget
def get_focus_path(self) -> list[int | str]:
"""
Return the .focus_position values starting from this container
and proceeding along each child widget until reaching a leaf
(non-container) widget.
"""
out = []
w = self
while True:
try:
p = w.focus_position
except IndexError:
return out
out.append(p)
w = w.focus.base_widget
def set_focus_path(self, positions: Iterable[int | str]) -> None:
"""
Set the .focus_position property starting from this container
widget and proceeding along newly focused child widgets. Any
failed assignment due do incompatible position types or invalid
positions will raise an IndexError.
This method may be used to restore a particular widget to the
focus by passing in the value returned from an earlier call to
get_focus_path().
positions -- sequence of positions
"""
w: Widget = self
for p in positions:
if p != w.focus_position:
w.focus_position = p # modifies w.focus
w = w.focus.base_widget # type: ignore[assignment]
def get_focus_widgets(self) -> list[Widget]:
"""
Return the .focus values starting from this container
and proceeding along each child widget until reaching a leaf
(non-container) widget.
Note that the list does not contain the topmost container widget
(i.e., on which this method is called), but does include the
lowest leaf widget.
"""
out = []
w = self
while (w := w.base_widget.focus) is not None:
out.append(w)
return out
@property
@abc.abstractmethod
def focus(self) -> Widget:
"""
Read-only property returning the child widget in focus for
container widgets. This default implementation
always returns ``None``, indicating that this widget has no children.
"""
class WidgetContainerListContentsMixin:
"""
Mixin class for widget containers whose positions are indexes into
a list available as self.contents.
"""
def __iter__(self) -> Iterator[int]:
"""
Return an iterable of positions for this container from first
to last.
"""
return iter(range(len(self.contents)))
def __reversed__(self) -> Iterator[int]:
"""
Return an iterable of positions for this container from last
to first.
"""
return iter(range(len(self.contents) - 1, -1, -1))
def __len__(self) -> int:
return len(self.contents)
@property
@abc.abstractmethod
def contents(self) -> list[tuple[Widget, typing.Any]]:
"""The contents of container as a list of (widget, options)"""
@contents.setter
def contents(self, new_contents: list[tuple[Widget, typing.Any]]) -> None:
"""The contents of container as a list of (widget, options)"""
@property
@abc.abstractmethod
def focus_position(self) -> int | None:
"""
index of child widget in focus.
"""
@focus_position.setter
def focus_position(self, position: int) -> None:
"""
index of child widget in focus.
"""