# Urwid Window-Icon-Menu-Pointer-style widget classes # Copyright (C) 2004-2011 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: https://urwid.org/ from __future__ import annotations import typing from urwid.canvas import CompositeCanvas from .constants import Sizing from .overlay import Overlay from .widget import delegate_to_widget_mixin from .widget_decoration import WidgetDecoration if typing.TYPE_CHECKING: from urwid.canvas import Canvas from .widget import Widget class PopUpLauncher(delegate_to_widget_mixin("_original_widget"), WidgetDecoration): def __init__(self, original_widget: Widget) -> None: super().__init__(original_widget) self._pop_up_widget = None def create_pop_up(self): """ Subclass must override this method and return a widget to be used for the pop-up. This method is called once each time the pop-up is opened. """ raise NotImplementedError("Subclass must override this method") def get_pop_up_parameters(self): """ Subclass must override this method and have it return a dict, eg: {'left':0, 'top':1, 'overlay_width':30, 'overlay_height':4} This method is called each time this widget is rendered. """ raise NotImplementedError("Subclass must override this method") def open_pop_up(self) -> None: self._pop_up_widget = self.create_pop_up() self._invalidate() def close_pop_up(self) -> None: self._pop_up_widget = None self._invalidate() def render(self, size, focus: bool = False) -> CompositeCanvas | Canvas: canv = super().render(size, focus) if self._pop_up_widget: canv = CompositeCanvas(canv) canv.set_pop_up(self._pop_up_widget, **self.get_pop_up_parameters()) return canv class PopUpTarget(WidgetDecoration): # FIXME: this whole class is a terrible hack and must be fixed # when layout and rendering are separated _sizing = frozenset((Sizing.BOX,)) _selectable = True def __init__(self, original_widget: Widget) -> None: super().__init__(original_widget) self._pop_up = None self._current_widget = self._original_widget def _update_overlay(self, size: tuple[int, int], focus: bool) -> None: canv = self._original_widget.render(size, focus=focus) self._cache_original_canvas = canv # imperfect performance hack pop_up = canv.get_pop_up() if pop_up: left, top, (w, overlay_width, overlay_height) = pop_up if self._pop_up != w: self._pop_up = w self._current_widget = Overlay( w, self._original_widget, ("fixed left", left), overlay_width, ("fixed top", top), overlay_height, ) else: self._current_widget.set_overlay_parameters( ("fixed left", left), overlay_width, ("fixed top", top), overlay_height, ) else: self._pop_up = None self._current_widget = self._original_widget def render(self, size: tuple[int, int], focus: bool = False) -> Canvas: self._update_overlay(size, focus) return self._current_widget.render(size, focus=focus) def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None: self._update_overlay(size, True) return self._current_widget.get_cursor_coords(size) def get_pref_col(self, size: tuple[int, int]) -> int: self._update_overlay(size, True) return self._current_widget.get_pref_col(size) def keypress(self, size: tuple[int, int], key: str) -> str | None: self._update_overlay(size, True) return self._current_widget.keypress(size, key) def move_cursor_to_coords(self, size: tuple[int, int], x: int, y: int): self._update_overlay(size, True) return self._current_widget.move_cursor_to_coords(size, x, y) def mouse_event(self, size: tuple[int, int], event, button: int, x: int, y: int, focus: bool) -> bool | None: self._update_overlay(size, focus) return self._current_widget.mouse_event(size, event, button, x, y, focus) def pack(self, size: tuple[int, int] | None = None, focus: bool = False) -> tuple[int, int]: self._update_overlay(size, focus) return self._current_widget.pack(size) def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test()