codekingpro/portable-devtools
114k
1# Urwid Window-Icon-Menu-Pointer-style widget classes2# Copyright (C) 2004-2011 Ian Ward3#4# This library is free software; you can redistribute it and/or5# modify it under the terms of the GNU Lesser General Public6# License as published by the Free Software Foundation; either7# version 2.1 of the License, or (at your option) any later version.8#9# This library is distributed in the hope that it will be useful,10# but WITHOUT ANY WARRANTY; without even the implied warranty of11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU12# Lesser General Public License for more details.13#14# You should have received a copy of the GNU Lesser General Public15# License along with this library; if not, write to the Free Software16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA17#18# Urwid web site: https://urwid.org/19 20 21from __future__ import annotations22 23import typing24 25from urwid.canvas import CompositeCanvas26 27from .constants import Align, Sizing, VAlign28from .overlay import Overlay29from .widget import delegate_to_widget_mixin30from .widget_decoration import WidgetDecoration31 32if typing.TYPE_CHECKING:33 from typing_extensions import TypedDict34 35 from urwid.canvas import Canvas36 37 from .widget import Widget38 39 class PopUpParametersModel(TypedDict):40 left: int41 top: int42 overlay_width: int43 overlay_height: int44 45 46WrappedWidget = typing.TypeVar("WrappedWidget")47 48 49class PopUpLauncher(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[WrappedWidget]):50 def __init__(self, original_widget: [WrappedWidget]) -> None:51 super().__init__(original_widget)52 self._pop_up_widget = None53 54 def create_pop_up(self) -> Widget:55 """56 Subclass must override this method and return a widget57 to be used for the pop-up. This method is called once each time58 the pop-up is opened.59 """60 raise NotImplementedError("Subclass must override this method")61 62 def get_pop_up_parameters(self) -> PopUpParametersModel:63 """64 Subclass must override this method and have it return a dict, eg:65 66 {'left':0, 'top':1, 'overlay_width':30, 'overlay_height':4}67 68 This method is called each time this widget is rendered.69 """70 raise NotImplementedError("Subclass must override this method")71 72 def open_pop_up(self) -> None:73 self._pop_up_widget = self.create_pop_up()74 self._invalidate()75 76 def close_pop_up(self) -> None:77 self._pop_up_widget = None78 self._invalidate()79 80 def render(self, size, focus: bool = False) -> CompositeCanvas | Canvas:81 canv = super().render(size, focus)82 if self._pop_up_widget:83 canv = CompositeCanvas(canv)84 canv.set_pop_up(self._pop_up_widget, **self.get_pop_up_parameters())85 return canv86 87 88class PopUpTarget(WidgetDecoration[WrappedWidget]):89 # FIXME: this whole class is a terrible hack and must be fixed when layout and rendering are separated90 _sizing = frozenset((Sizing.BOX,))91 _selectable = True92 93 def __init__(self, original_widget: WrappedWidget) -> None:94 super().__init__(original_widget)95 self._pop_up = None96 self._current_widget = self._original_widget97 98 def _update_overlay(self, size: tuple[int, int], focus: bool) -> None:99 canv = self._original_widget.render(size, focus=focus)100 self._cache_original_canvas = canv # imperfect performance hack101 102 if pop_up := canv.get_pop_up():103 left, top, (w, overlay_width, overlay_height) = pop_up104 if self._pop_up != w:105 self._pop_up = w106 self._current_widget = Overlay(107 top_w=w,108 bottom_w=self._original_widget,109 align=Align.LEFT,110 width=overlay_width,111 valign=VAlign.TOP,112 height=overlay_height,113 left=left,114 top=top,115 )116 else:117 self._current_widget.set_overlay_parameters(118 align=Align.LEFT,119 width=overlay_width,120 valign=VAlign.TOP,121 height=overlay_height,122 left=left,123 top=top,124 )125 else:126 self._pop_up = None127 self._current_widget = self._original_widget128 129 def render(self, size: tuple[int, int], focus: bool = False) -> Canvas:130 self._update_overlay(size, focus)131 return self._current_widget.render(size, focus=focus)132 133 def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:134 self._update_overlay(size, True)135 return self._current_widget.get_cursor_coords(size)136 137 def get_pref_col(self, size: tuple[int, int]) -> int:138 self._update_overlay(size, True)139 return self._current_widget.get_pref_col(size)140 141 def keypress(self, size: tuple[int, int], key: str) -> str | None:142 self._update_overlay(size, True)143 return self._current_widget.keypress(size, key)144 145 def move_cursor_to_coords(self, size: tuple[int, int], x: int, y: int):146 self._update_overlay(size, True)147 return self._current_widget.move_cursor_to_coords(size, x, y)148 149 def mouse_event(150 self,151 size: tuple[int, int],152 event: str,153 button: int,154 col: int,155 row: int,156 focus: bool,157 ) -> bool | None:158 self._update_overlay(size, focus)159 return self._current_widget.mouse_event(size, event, button, col, row, focus)160 161 def pack(self, size: tuple[int, int] | None = None, focus: bool = False) -> tuple[int, int]:162 self._update_overlay(size, focus)163 return self._current_widget.pack(size)164 165 166def _test():167 import doctest168 169 doctest.testmod()170 171 172if __name__ == "__main__":173 _test()174 