codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import typing4import warnings5 6from urwid.canvas import CompositeCanvas7 8from .widget import Widget, WidgetError, WidgetWarning, delegate_to_widget_mixin9 10if typing.TYPE_CHECKING:11 from typing_extensions import Literal12 13 from .constants import Sizing14 15 16__all__ = (17 "WidgetDecoration",18 "WidgetDisable",19 "WidgetError",20 "WidgetPlaceholder",21 "WidgetWarning",22 "delegate_to_widget_mixin",23)24 25WrappedWidget = typing.TypeVar("WrappedWidget")26 27 28class WidgetDecoration(Widget, typing.Generic[WrappedWidget]): # pylint: disable=abstract-method29 """30 original_widget -- the widget being decorated31 32 This is a base class for decoration widgets,33 widgets that contain one or more widgets and only ever have a single focus.34 This type of widget will affect the display or behaviour of the original_widget,35 but it is not part of determining a chain of focus.36 37 Don't actually do this -- use a WidgetDecoration subclass instead, these are not real widgets:38 39 >>> from urwid import Text40 >>> WidgetDecoration(Text("hi"))41 <WidgetDecoration fixed/flow widget <Text fixed/flow widget 'hi'>>42 43 .. Warning:44 WidgetDecoration do not implement ``render`` method.45 Implement it or forward to the widget in the subclass.46 """47 48 def __init__(self, original_widget: WrappedWidget) -> None:49 # TODO(Aleksei): reduce amount of multiple inheritance usage50 # Special case: subclasses with multiple inheritance causes `super` call wrong way51 # Call parent __init__ explicit52 Widget.__init__(self)53 if not isinstance(original_widget, Widget):54 obj_class_path = f"{original_widget.__class__.__module__}.{original_widget.__class__.__name__}"55 warnings.warn(56 f"{obj_class_path} is not subclass of Widget",57 DeprecationWarning,58 stacklevel=2,59 )60 self._original_widget = original_widget61 62 def _repr_words(self) -> list[str]:63 return [*super()._repr_words(), repr(self._original_widget)]64 65 @property66 def original_widget(self) -> WrappedWidget:67 return self._original_widget68 69 @original_widget.setter70 def original_widget(self, original_widget: WrappedWidget) -> None:71 self._original_widget = original_widget72 self._invalidate()73 74 @property75 def base_widget(self) -> Widget:76 """77 Return the widget without decorations.78 79 If there is only one Decoration, then this is the same as original_widget.80 81 >>> from urwid import Text82 >>> t = Text("hello")83 >>> wd1 = WidgetDecoration(t)84 >>> wd2 = WidgetDecoration(wd1)85 >>> wd3 = WidgetDecoration(wd2)86 >>> wd3.original_widget is wd287 True88 >>> wd3.base_widget is t89 True90 """91 visited = {self}92 w = self93 while (w := getattr(w, "_original_widget", w)) not in visited:94 visited.add(w)95 return w96 97 def selectable(self) -> bool:98 return self._original_widget.selectable()99 100 def sizing(self) -> frozenset[Sizing]:101 return self._original_widget.sizing()102 103 104class WidgetPlaceholder(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[WrappedWidget]):105 """106 This is a do-nothing decoration widget that can be used for swapping107 between widgets without modifying the container of this widget.108 109 This can be useful for making an interface with a number of distinct110 pages or for showing and hiding menu or status bars.111 112 The widget displayed is stored as the self.original_widget property and113 can be changed by assigning a new widget to it.114 """115 116 117class WidgetDisable(WidgetDecoration[WrappedWidget]):118 """119 A decoration widget that disables interaction with the widget it120 wraps. This widget always passes focus=False to the wrapped widget,121 even if it somehow does become the focus.122 """123 124 no_cache: typing.ClassVar[list[str]] = ["rows"]125 ignore_focus = True126 127 def selectable(self) -> Literal[False]:128 return False129 130 def rows(self, size: tuple[int], focus: bool = False) -> int:131 return self._original_widget.rows(size, False)132 133 def sizing(self) -> frozenset[Sizing]:134 return self._original_widget.sizing()135 136 def pack(self, size, focus: bool = False) -> tuple[int, int]:137 return self._original_widget.pack(size, False)138 139 def render(self, size, focus: bool = False) -> CompositeCanvas:140 canv = self._original_widget.render(size, False)141 return CompositeCanvas(canv)142 