Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
urwid-uikit / grid.py
Size: Mime:
from typing import Iterator, Optional
from urwid import AttrMap, Filler, GridFlow, Widget, WidgetWrap
from urwid.command_map import (
    command_map,
    CommandMap,
    CURSOR_DOWN,
    CURSOR_LEFT,
    CURSOR_RIGHT,
    CURSOR_UP,
    CURSOR_MAX_LEFT,
    CURSOR_MAX_RIGHT,
)

from .mixins import ObjectContainerMixin

__all__ = ("Grid", "ObjectGrid")


class Grid(WidgetWrap):
    """Grid that shows a set of widgets and has a single focused widget that is
    displayed differently.
    """

    _command_map: CommandMap = command_map

    _grid_flow: GridFlow
    _selectable: bool = True

    def __init__(
        self,
        cell_width: int,
        h_sep: int = 0,
        v_sep: int = 0,
        align: str = "left",
        valign: str = "top",
    ):
        """Constructor."""
        self._grid_flow = GridFlow(
            [], cell_width=cell_width, h_sep=h_sep, v_sep=v_sep, align=align
        )
        filler = Filler(self._grid_flow, valign=valign)
        super().__init__(filler)

    def add_widget(self, widget: Widget, index: int = None) -> None:
        """Adds a new widget to the list box.

        Parameters:
            widget: the widget to add
            index: the insertion index; ``None`` means the end of the grid
        """
        # Wrap the widget in an AttrMap before it actually gets inserted
        wrapped_widget = AttrMap(widget, "", "list focus")
        options = self._grid_flow.options()

        # Do the insertion
        if index is None:
            self._grid_flow.contents.append((wrapped_widget, options))
        else:
            self._grid_flow.contents.insert(index, (wrapped_widget, options))

    @property
    def command_map(self) -> CommandMap:
        return self._command_map

    @property
    def contents(self):
        return self._grid_flow.contents

    @property
    def focus(self):
        return self._grid_flow.focus

    @property
    def focus_position(self) -> int:
        return self._grid_flow.focus_position

    @focus_position.setter
    def focus_position(self, value):
        self._grid_flow.focus_position = value

    @property
    def focused_widget(self) -> Optional[Widget]:
        """Returns the focused widget of the list.

        Returns:
            urwid.Widget: the focused widget
        """
        focused_widget = self.focus
        if hasattr(focused_widget, "base_widget"):
            focused_widget = focused_widget.base_widget
        return focused_widget

    def iterwidgets(self) -> Iterator[Widget]:
        """Iterates over all the widgets objects in the list."""
        for existing_widget, _ in self._grid_flow.contents:
            # Contrary to what the urwid.AttrMap documentation says, AttrMap
            # objects do not forward properties transparently to the wrapped
            # widgets, so we need the base_widget property here (which is
            # the identity function for non-decoration widgets)
            yield existing_widget.base_widget

    def keypress(self, size, key: str) -> Optional[str]:
        command = self.command_map[key]
        delta, new_pos = None, None

        if command == CURSOR_LEFT:
            delta = -1
        elif command == CURSOR_RIGHT:
            delta = 1
        elif command == CURSOR_UP:
            delta = -self._get_num_columns_from_size(size)
        elif command == CURSOR_DOWN:
            delta = self._get_num_columns_from_size(size)
        elif command == CURSOR_MAX_LEFT:
            num_cols = self._get_num_columns_from_size(size)
            if num_cols > 0:
                new_pos = (self._get_focus_position_safe() // num_cols) * num_cols
        elif command == CURSOR_MAX_RIGHT:
            num_cols = self._get_num_columns_from_size(size)
            if num_cols > 0:
                new_pos = (
                    self._get_focus_position_safe() // num_cols + 1
                ) * num_cols - 1
                new_pos = min(new_pos, len(self.contents) - 1)
        else:
            # Keypress not handled here
            return key

        try:
            if new_pos is not None:
                self.focus_position = new_pos
            elif delta is not None:
                self.focus_position = self._get_focus_position_safe() + delta
        except IndexError:
            # new index is invalid
            pass

    def refresh(self) -> None:
        """Refreshes all the widgets in the list."""
        for widget in self.iterwidgets():
            if hasattr(widget, "refresh"):
                widget.refresh()

    def remove_widget(self, widget: Widget) -> None:
        """Removes the given widget from the list box.

        Parameters:
            widget: the widget to remove
        """
        removal_index = None
        for index, existing_widget in enumerate(self.iterwidgets()):
            if existing_widget is widget:
                removal_index = index
                break

        if removal_index is not None:
            self.remove_widget_at(removal_index)

    def remove_widget_at(self, index: int) -> None:
        """Removes the widget with the given index from the list box.

        Parameters:
            index: the index of the widget to remove
        """
        self._grid_flow.contents.pop(index)

    def _get_focus_position_safe(self) -> int:
        try:
            return self.focus_position
        except IndexError:
            return 0

    def _get_num_columns_from_size(self, size) -> int:
        w, _ = size
        return w // (self._grid_flow.h_sep + self._grid_flow.cell_width)


class ObjectGrid(Grid, ObjectContainerMixin):
    """Grid that shows a set of widgets such that each widget is associated to a
    unique object, and the order of the widgets in the grid is determined by a
    function that derives a comparable key from each unique object in the list.

    This is an abstract base class. Derived classes must override at least
    ``_create_widget_for_item()``.
    """

    def __init__(self, *args, **kwds):
        """Constructor."""
        super().__init__(*args, **kwds)
        ObjectContainerMixin.__init__(self)