codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import abc4import enum5import typing6 7from .constants import Sizing, WHSettings8 9if typing.TYPE_CHECKING:10 from collections.abc import Iterable, Iterator11 12 from .widget import Widget13 14 15# Ideally, we would like to use an IntFlag coupled with enum.auto().16# However, doing many bitwise operations (which happens when nesting too many17# widgets ...) on IntFlag is orders of magnitude slower than doing the same18# operations on IntEnum.19class _ContainerElementSizingFlag(enum.IntEnum):20 # fmt: off21 NONE = 0b00000022 BOX = 0b00000123 FLOW = 0b00001024 FIXED = 0b00010025 WH_WEIGHT = 0b00100026 WH_PACK = 0b01000027 WH_GIVEN = 0b10000028 # fmt: on29 30 @staticmethod31 def reverse_flag(bitfield: int) -> tuple[frozenset[Sizing], WHSettings | None]:32 """Get flag in public API format."""33 sizing: set[Sizing] = set()34 35 if bitfield & _ContainerElementSizingFlag.BOX:36 sizing.add(Sizing.BOX)37 if bitfield & _ContainerElementSizingFlag.FLOW:38 sizing.add(Sizing.FLOW)39 if bitfield & _ContainerElementSizingFlag.FIXED:40 sizing.add(Sizing.FIXED)41 42 if bitfield & _ContainerElementSizingFlag.WH_WEIGHT:43 return frozenset(sizing), WHSettings.WEIGHT44 if bitfield & _ContainerElementSizingFlag.WH_PACK:45 return frozenset(sizing), WHSettings.PACK46 if bitfield & _ContainerElementSizingFlag.WH_GIVEN:47 return frozenset(sizing), WHSettings.GIVEN48 return frozenset(sizing), None49 50 @staticmethod51 def log_string(bitfield: int) -> str:52 """Get desctiprion in public API format."""53 sizing, render = _ContainerElementSizingFlag.reverse_flag(bitfield)54 render_string = f" {render.upper()}" if render else ""55 return "|".join(sorted(mode.upper() for mode in sizing)) + render_string56 57 58class WidgetContainerMixin:59 """60 Mixin class for widget containers implementing common container methods61 """62 63 def __getitem__(self, position) -> Widget:64 """65 Container short-cut for self.contents[position][0].base_widget66 which means "give me the child widget at position without any67 widget decorations".68 69 This allows for concise traversal of nested container widgets70 such as:71 72 my_widget[position0][position1][position2] ...73 """74 return self.contents[position][0].base_widget75 76 def get_focus_path(self) -> list[int | str]:77 """78 Return the .focus_position values starting from this container79 and proceeding along each child widget until reaching a leaf80 (non-container) widget.81 """82 out = []83 w = self84 while True:85 try:86 p = w.focus_position87 except IndexError:88 return out89 out.append(p)90 w = w.focus.base_widget91 92 def set_focus_path(self, positions: Iterable[int | str]) -> None:93 """94 Set the .focus_position property starting from this container95 widget and proceeding along newly focused child widgets. Any96 failed assignment due do incompatible position types or invalid97 positions will raise an IndexError.98 99 This method may be used to restore a particular widget to the100 focus by passing in the value returned from an earlier call to101 get_focus_path().102 103 positions -- sequence of positions104 """105 w: Widget = self106 for p in positions:107 if p != w.focus_position:108 w.focus_position = p # modifies w.focus109 w = w.focus.base_widget # type: ignore[assignment]110 111 def get_focus_widgets(self) -> list[Widget]:112 """113 Return the .focus values starting from this container114 and proceeding along each child widget until reaching a leaf115 (non-container) widget.116 117 Note that the list does not contain the topmost container widget118 (i.e., on which this method is called), but does include the119 lowest leaf widget.120 """121 out = []122 w = self123 while (w := w.base_widget.focus) is not None:124 out.append(w)125 126 return out127 128 @property129 @abc.abstractmethod130 def focus(self) -> Widget:131 """132 Read-only property returning the child widget in focus for133 container widgets. This default implementation134 always returns ``None``, indicating that this widget has no children.135 """136 137 138class WidgetContainerListContentsMixin:139 """140 Mixin class for widget containers whose positions are indexes into141 a list available as self.contents.142 """143 144 def __iter__(self) -> Iterator[int]:145 """146 Return an iterable of positions for this container from first147 to last.148 """149 return iter(range(len(self.contents)))150 151 def __reversed__(self) -> Iterator[int]:152 """153 Return an iterable of positions for this container from last154 to first.155 """156 return iter(range(len(self.contents) - 1, -1, -1))157 158 def __len__(self) -> int:159 return len(self.contents)160 161 @property162 @abc.abstractmethod163 def contents(self) -> list[tuple[Widget, typing.Any]]:164 """The contents of container as a list of (widget, options)"""165 166 @contents.setter167 def contents(self, new_contents: list[tuple[Widget, typing.Any]]) -> None:168 """The contents of container as a list of (widget, options)"""169 170 @property171 @abc.abstractmethod172 def focus_position(self) -> int | None:173 """174 index of child widget in focus.175 """176 177 @focus_position.setter178 def focus_position(self, position: int) -> None:179 """180 index of child widget in focus.181 """182 