summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Stepanov <penguinolog@users.noreply.github.com>2023-04-12 07:47:14 +0200
committerGitHub <noreply@github.com>2023-04-12 07:47:14 +0200
commit8a0a6d166e4cef88a7e7b644abee8712eacee7f6 (patch)
treea96c0ccd10f838bcbbc00482b04b547c0b1a83c5
parent0c0ea377ab9b418cbb5233fa6e178dd05f1f4e5a (diff)
downloadurwid-8a0a6d166e4cef88a7e7b644abee8712eacee7f6.tar.gz
Deprecate legacy property creation (#533)
* Deprecate legacy property creation * Drop long time ago removed methods (never returning methods) * Move large part of property implementations under `@property` * Emit `PendingDeprecationWarning` for old compatibility code for public methods used as core for property and methods for compatibility * Emit `DeprecationWarning` for private methods used in property construction using `property()` call Due to amount of copy-paste like changes, for containers shared part is moved to the existing base classes Add `__len__` to the list based containers. Related #445 Fix typo in type annotation for `Frame.mouse_event` * Update urwid/canvas.py Co-authored-by: Ian Ward <ian@excess.org> * Update urwid/canvas.py Co-authored-by: Ian Ward <ian@excess.org> * Update urwid/tests/test_container.py Co-authored-by: Ian Ward <ian@excess.org> * Fix typo in test name * Frame `header`, `body` and `footer` also has property and methods from pre-property era Make consistent with other containers --------- Co-authored-by: Aleksei Stepanov <alekseis@nvidia.com> Co-authored-by: Ian Ward <ian@excess.org>
-rwxr-xr-xexamples/calc.py50
-rwxr-xr-xexamples/dialog.py22
-rw-r--r--urwid/canvas.py35
-rwxr-xr-xurwid/container.py926
-rwxr-xr-xurwid/decoration.py183
-rwxr-xr-xurwid/display_common.py61
-rwxr-xr-xurwid/graphics.py18
-rw-r--r--urwid/listbox.py56
-rwxr-xr-xurwid/main_loop.py45
-rwxr-xr-xurwid/monitored_list.py63
-rw-r--r--urwid/tests/test_container.py29
-rw-r--r--urwid/widget.py51
12 files changed, 1137 insertions, 402 deletions
diff --git a/examples/calc.py b/examples/calc.py
index bf31ba6..b83cc07 100755
--- a/examples/calc.py
+++ b/examples/calc.py
@@ -570,7 +570,7 @@ class CalcDisplay:
def __init__(self):
self.columns = urwid.Columns([HelpColumn(), CellColumn("A")], 1)
self.col_list = self.columns.widget_list
- self.columns.set_focus_column( 1 )
+ self.columns.focus_position = 1
view = urwid.AttrWrap(self.columns, 'body')
self.view = urwid.Frame(view) # for showing messages
self.col_link = {}
@@ -629,7 +629,7 @@ class CalcDisplay:
if key is None:
return
- if self.columns.get_focus_column() == 0:
+ if self.columns.focus_position == 0:
if key not in ('up','down','page up','page down'):
raise CalcEvent(E_invalid_in_help_col)
@@ -648,41 +648,41 @@ class CalcDisplay:
if key.upper() in COLUMN_KEYS:
# column switch
i = COLUMN_KEYS.index(key.upper())
- if i >= len( self.col_list ):
+ if i >= len(self.columns):
raise CalcEvent(E_no_such_column % key.upper())
- self.columns.set_focus_column( i )
+ self.columns.focus_position = i
return
elif key == "(":
# open a new column
- if len( self.col_list ) >= len(COLUMN_KEYS):
+ if len(self.columns) >= len(COLUMN_KEYS):
raise CalcEvent(E_no_more_columns)
- i = self.columns.get_focus_column()
+ i = self.columns.focus_position
if i == 0:
# makes no sense in help column
return key
- col = self.col_list[i]
- new_letter = COLUMN_KEYS[len(self.col_list)]
- parent, child = col.create_child( new_letter )
+ col = self.columns.contents[i][0]
+ new_letter = COLUMN_KEYS[len(self.columns)]
+ parent, child = col.create_child(new_letter )
if child is None:
# something invalid in focus
return key
self.col_list.append(child)
self.set_link( parent, col, child )
- self.columns.set_focus_column(len(self.col_list)-1)
+ self.columns.focus_position = len(self.columns)-1
elif key == ")":
- i = self.columns.get_focus_column()
+ i = self.columns.focus_position
if i == 0:
# makes no sense in help column
return key
- col = self.col_list[i]
+ col = self.columns.contents[i][0]
parent, pcol = self.get_parent( col )
if parent is None:
# column has no parent
raise CalcEvent(E_no_parent_column)
new_i = self.col_list.index( pcol )
- self.columns.set_focus_column( new_i )
+ self.columns.focus_position = new_i
else:
return key
@@ -704,7 +704,7 @@ class CalcDisplay:
"""Return True if the column passed is empty."""
i = COLUMN_KEYS.index(letter)
- col = self.col_list[i]
+ col = self.columns.contents[i][0]
return col.is_empty()
@@ -712,19 +712,19 @@ class CalcDisplay:
"""Delete the column with the given letter."""
i = COLUMN_KEYS.index(letter)
- col = self.col_list[i]
+ col = self.columns.contents[i][0]
parent, pcol = self.get_parent( col )
- f = self.columns.get_focus_column()
+ f = self.columns.focus_position
if f == i:
# move focus to the parent column
f = self.col_list.index(pcol)
- self.columns.set_focus_column(f)
+ self.columns.focus_position = f
parent.remove_child()
pcol.update_results(parent)
- del self.col_list[i]
+ del self.columns.contents[i]
# delete children of this column
keep_right_cols = []
@@ -742,8 +742,8 @@ class CalcDisplay:
self.col_list[i:] = keep_right_cols
# fix the letter assignments
- for j in range(i, len(self.col_list)):
- col = self.col_list[j]
+ for j in range(i, len(self.columns)):
+ col = self.columns.contents[j][0]
# fix the column heading
col.set_letter( COLUMN_KEYS[j] )
parent, pcol = self.get_parent( col )
@@ -753,8 +753,8 @@ class CalcDisplay:
def update_parent_columns(self):
"Update the parent columns of the current focus column."
- f = self.columns.get_focus_column()
- col = self.col_list[f]
+ f = self.columns.focus_position
+ col = self.columns.contents[f][0]
while 1:
parent, pcol = self.get_parent(col)
if pcol is None:
@@ -765,13 +765,11 @@ class CalcDisplay:
return
col = pcol
-
def get_expression_result(self):
"""Return (expression, result) as strings."""
- col = self.col_list[1]
- return col.get_expression(), "%d"%col.get_result()
-
+ col = self.columns.contents[1][0]
+ return col.get_expression(), f"{col.get_result():d}"
class CalcNumLayout(urwid.TextLayout):
diff --git a/examples/dialog.py b/examples/dialog.py
index ec8c8e0..47ec0f3 100755
--- a/examples/dialog.py
+++ b/examples/dialog.py
@@ -118,16 +118,16 @@ class InputDialogDisplay(DialogDisplay):
DialogDisplay.__init__(self, text, height, width, body)
- self.frame.set_focus('body')
+ self.frame.focus_position = 'body'
def unhandled_key(self, size, k):
if k in ('up','page up'):
- self.frame.set_focus('body')
+ self.frame.focus_position = 'body'
if k in ('down','page down'):
- self.frame.set_focus('footer')
+ self.frame.focus_position = 'footer'
if k == 'enter':
# pass enter to the "ok" button
- self.frame.set_focus('footer')
+ self.frame.focus_position = 'footer'
self.view.keypress( size, k )
def on_exit(self, exitcode):
@@ -148,9 +148,9 @@ class TextDialogDisplay(DialogDisplay):
def unhandled_key(self, size, k):
if k in ('up','page up','down','page down'):
- self.frame.set_focus('body')
+ self.frame.focus_position = 'body'
self.view.keypress( size, k )
- self.frame.set_focus('footer')
+ self.frame.focus_position = 'footer'
class ListDialogDisplay(DialogDisplay):
@@ -178,17 +178,17 @@ class ListDialogDisplay(DialogDisplay):
lb = urwid.AttrWrap( lb, "selectable" )
DialogDisplay.__init__(self, text, height, width, lb )
- self.frame.set_focus('body')
+ self.frame.focus_position = 'body'
def unhandled_key(self, size, k):
if k in ('up','page up'):
- self.frame.set_focus('body')
+ self.frame.focus_position = 'body'
if k in ('down','page down'):
- self.frame.set_focus('footer')
+ self.frame.focus_position = 'footer'
if k == 'enter':
# pass enter to the "ok" button
- self.frame.set_focus('footer')
- self.buttons.set_focus(0)
+ self.frame.focus_position = 'footer'
+ self.buttons.focus_position = 0
self.view.keypress( size, k )
def on_exit(self, exitcode):
diff --git a/urwid/canvas.py b/urwid/canvas.py
index 7838ec2..ed71c54 100644
--- a/urwid/canvas.py
+++ b/urwid/canvas.py
@@ -23,6 +23,7 @@
from __future__ import annotations
import typing
+import warnings
import weakref
from collections.abc import Sequence
@@ -203,10 +204,6 @@ class Canvas:
_renamed_error = CanvasError("The old Canvas class is now called "
"TextCanvas. Canvas is now the base class for all canvas "
"classes.")
- _old_repr_error = CanvasError("The internal representation of "
- "canvases is no longer stored as .text, .attr, and .cs "
- "lists, please see the TextCanvas class for the new "
- "representation of canvas content.")
def __init__(
self,
@@ -240,23 +237,35 @@ class Canvas:
raise self._finalized_error
self._widget_info = widget, size, focus
- def _get_widget_info(self):
+ @property
+ def widget_info(self):
return self._widget_info
- widget_info = property(_get_widget_info)
-
- def _raise_old_repr_error(self, val=None) -> typing.NoReturn:
- raise self._old_repr_error
- def _text_content(self):
+ def _get_widget_info(self):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_widget_info` is deprecated, "
+ f"please use property `{self.__class__.__name__}.widget_info`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.widget_info
+
+ @property
+ def text(self) -> list[bytes]:
"""
Return the text content of the canvas as a list of strings,
one for each row.
"""
return [b''.join([text for (attr, cs, text) in row]) for row in self.content()]
- text = property(_text_content, _raise_old_repr_error)
- attr = property(_raise_old_repr_error, _raise_old_repr_error)
- cs = property(_raise_old_repr_error, _raise_old_repr_error)
+ def _text_content(self):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._text_content` is deprecated, "
+ f"please use property `{self.__class__.__name__}.text`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.text
def content(
self,
diff --git a/urwid/container.py b/urwid/container.py
index 9914e9b..6923388 100755
--- a/urwid/container.py
+++ b/urwid/container.py
@@ -22,8 +22,10 @@
from __future__ import annotations
+import abc
import typing
-from collections.abc import Iterable, Sequence
+import warnings
+from collections.abc import Iterable, Iterator, Sequence
from itertools import chain, repeat
from urwid.canvas import CanvasCombine, CanvasJoin, CanvasOverlay, CompositeCanvas, SolidCanvas
@@ -70,7 +72,7 @@ class WidgetContainerMixin:
"""
Mixin class for widget containers implementing common container methods
"""
- def __getitem__(self, position):
+ def __getitem__(self, position) -> Widget:
"""
Container short-cut for self.contents[position][0].base_widget
which means "give me the child widget at position without any
@@ -136,26 +138,110 @@ class WidgetContainerMixin:
return out
out.append(w)
+ @property
+ @abc.abstractmethod
+ def focus(self) -> Widget:
+ """
+ Read-only property returning the child widget in focus for
+ container widgets. This default implementation
+ always returns ``None``, indicating that this widget has no children.
+ """
+
+ def _get_focus(self) -> Widget:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._get_focus` is deprecated, "
+ f"please use `{self.__class__.__name__}.focus` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus
+
class WidgetContainerListContentsMixin:
"""
Mixin class for widget containers whose positions are indexes into
a list available as self.contents.
"""
- def __iter__(self):
+ def __iter__(self) -> Iterator[int]:
"""
Return an iterable of positions for this container from first
to last.
"""
return iter(range(len(self.contents)))
- def __reversed__(self):
+ def __reversed__(self) -> Iterator[int]:
"""
Return an iterable of positions for this container from last
to first.
"""
return iter(range(len(self.contents) - 1, -1, -1))
+ def __len__(self) -> int:
+ return len(self.contents)
+
+ @property
+ @abc.abstractmethod
+ def contents(self) -> list[tuple[Widget, typing.Any]]:
+ """The contents of container as a list of (widget, options)"""
+
+ @contents.setter
+ def contents(self, new_contents: list[tuple[Widget, typing.Any]]) -> None:
+ """The contents of container as a list of (widget, options)"""
+
+ def _get_contents(self) -> list[tuple[Widget, typing.Any]]:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._get_contents` is deprecated, "
+ f"please use `{self.__class__.__name__}.contents` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.contents
+
+ def _set_contents(self, c: list[tuple[Widget, typing.Any]]) -> None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._set_contents` is deprecated, "
+ f"please use `{self.__class__.__name__}.contents` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.contents = c
+
+ @property
+ @abc.abstractmethod
+ def focus_position(self) -> int | None:
+ """
+ index of child widget in focus.
+ """
+
+ @focus_position.setter
+ def focus_position(self, position: int) -> None:
+ """
+ index of child widget in focus.
+ """
+
+ def _get_focus_position(self) -> int | None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
+ f"please use `{self.__class__.__name__}.focus_position` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus_position
+
+ def _set_focus_position(self, position: int) -> None:
+ """
+ Set the widget in focus.
+
+ position -- index of child widget to be made focus
+ """
+ warnings.warn(
+ f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
+ f"please use `{self.__class__.__name__}.focus_position` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.focus_position = position
+
class GridFlowError(Exception):
pass
@@ -172,14 +258,14 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
def __init__(
self,
- cells: Sequence[Widget],
+ cells: Iterable[Widget],
cell_width: int,
h_sep: int,
v_sep: int,
align: Literal['left', 'center', 'right'] | tuple[Literal['relative'], int],
):
"""
- :param cells: list of flow widgets to display
+ :param cells: iterable of flow widgets to display
:param cell_width: column width for each cell
:param h_sep: blank columns between each cell horizontally
:param v_sep: blank rows between cells vertically
@@ -198,7 +284,7 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
self._cache_maxcol = None
super().__init__(None)
# set self._w to something other than None
- self.get_display_widget(((h_sep+cell_width)*len(cells),))
+ self.get_display_widget(((h_sep+cell_width)*len(self._contents),))
def _invalidate(self) -> None:
self._cache_maxcol = None
@@ -213,47 +299,97 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
except (TypeError, ValueError):
raise GridFlowError(f"added content invalid {item!r}")
- def _get_cells(self):
+ @property
+ def cells(self):
+ """
+ A list of the widgets in this GridFlow
+
+ .. note:: only for backwards compatibility. You should use the new
+ standard container property :attr:`contents` to modify GridFlow
+ contents.
+ """
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container property `contents` to modify GridFlow",
+ PendingDeprecationWarning,
+ stacklevel=2
+ )
ml = MonitoredList(w for w, t in self.contents)
+
def user_modified():
- self._set_cells(ml)
+ self.cells = ml
ml.set_modified_callback(user_modified)
return ml
- def _set_cells(self, widgets):
+ @cells.setter
+ def cells(self, widgets: Sequence[Widget]):
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container property `contents` to modify GridFlow",
+ PendingDeprecationWarning,
+ stacklevel=2
+ )
focus_position = self.focus_position
self.contents = [
(new, (GIVEN, self._cell_width)) for new in widgets]
if focus_position < len(widgets):
self.focus_position = focus_position
- cells = property(_get_cells, _set_cells, doc="""
- A list of the widgets in this GridFlow
- .. note:: only for backwards compatibility. You should use the new
- standard container property :attr:`contents` to modify GridFlow
- contents.
- """)
+ def _get_cells(self):
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container property `contents` to modify GridFlow",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.cells
+
+ def _set_cells(self, widgets: Sequence[Widget]):
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container property `contents` to modify GridFlow",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.cells = widgets
- def _get_cell_width(self) -> int:
+ @property
+ def cell_width(self) -> int:
+ """
+ The width of each cell in the GridFlow. Setting this value affects
+ all cells.
+ """
return self._cell_width
- def _set_cell_width(self, width: int) -> None:
+ @cell_width.setter
+ def cell_width(self, width: int) -> None:
focus_position = self.focus_position
self.contents = [
(w, (GIVEN, width)) for (w, options) in self.contents]
self.focus_position = focus_position
self._cell_width = width
- cell_width = property(_get_cell_width, _set_cell_width, doc="""
- The width of each cell in the GridFlow. Setting this value affects
- all cells.
- """)
- def _get_contents(self):
- return self._contents
+ def _get_cell_width(self) -> int:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_cell_width` is deprecated, "
+ f"please use property `{self.__class__.__name__}.cell_width`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.cell_width
- def _set_contents(self, c):
- self._contents[:] = c
- contents = property(_get_contents, _set_contents, doc="""
+ def _set_cell_width(self, width: int) -> None:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_cell_width` is deprecated, "
+ f"please use property `{self.__class__.__name__}.cell_width`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.cell_width = width
+
+ @property
+ def contents(self):
+ """
The contents of this GridFlow as a list of (widget, options)
tuples.
@@ -265,7 +401,12 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
widget will update automatically.
.. seealso:: Create new options tuples with the :meth:`options` method.
- """)
+ """
+ return self._contents
+
+ @contents.setter
+ def contents(self, c):
+ self._contents[:] = c
def options(
self,
@@ -284,7 +425,7 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
width_amount = self._cell_width
return (width_type, width_amount)
- def set_focus(self, cell: Widget | int):
+ def set_focus(self, cell: Widget | int) -> None:
"""
Set the cell in focus, for backwards compatibility.
@@ -294,9 +435,23 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
:param cell: contained element to focus
:type cell: Widget or int
"""
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus_position` to set the focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
if isinstance(cell, int):
- return self._set_focus_position(cell)
- return self._set_focus_cell(cell)
+ self.focus_position = cell
+ return
+ self.focus_cell = cell
+
+ @property
+ def focus(self) -> Widget | None:
+ """the child widget in focus or None when GridFlow is empty"""
+ if not self.contents:
+ return None
+ return self.contents[self.focus_position][0]
def get_focus(self):
"""
@@ -305,37 +460,62 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
.. note:: only for backwards compatibility. You may also use the new
standard container property :attr:`focus` to get the focus.
"""
- if not self.contents:
- return None
- return self.contents[self.focus_position][0]
- focus = property(get_focus,
- doc="the child widget in focus or None when GridFlow is empty")
-
- def _set_focus_cell(self, cell: Widget) -> None:
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus` to get the focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus
+
+ @property
+ def focus_cell(self):
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property"
+ "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus
+
+ @focus_cell.setter
+ def focus_cell(self, cell: Widget) -> None:
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property"
+ "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
for i, (w, options) in enumerate(self.contents):
if cell == w:
self.focus_position = i
return
raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")
- focus_cell = property(get_focus, _set_focus_cell, doc="""
- The widget in focus, for backwards compatibility.
- .. note:: only for backwards compatibility. You should use the new
- standard container property :attr:`focus` to get the widget in
- focus and :attr:`focus_position` to get/set the cell in focus by
- index.
- """)
+ def _set_focus_cell(self, cell: Widget) -> None:
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property"
+ "`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.focus_cell = cell
- def _get_focus_position(self) -> int | None:
+ @property
+ def focus_position(self) -> int | None:
"""
- Return the index of the widget in focus or None if this GridFlow is
- empty.
+ index of child widget in focus.
+ Raises :exc:`IndexError` if read when GridFlow is empty, or when set to an invalid index.
"""
if not self.contents:
raise IndexError("No focus_position, GridFlow is empty")
return self.contents.focus
- def _set_focus_position(self, position: int) -> None:
+ @focus_position.setter
+ def focus_position(self, position: int) -> None:
"""
Set the widget in focus.
@@ -347,10 +527,6 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi
except (TypeError, IndexError):
raise IndexError(f"No GridFlow child widget at position {position}")
self.contents.focus = position
- focus_position = property(_get_focus_position, _set_focus_position, doc="""
- index of child widget in focus. Raises :exc:`IndexError` if read when
- GridFlow is empty, or when set to an invalid index.
- """)
def get_display_widget(self, size: tuple[int]) -> Divider | Pile:
"""
@@ -697,21 +873,24 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
"""Pass keypress to top_w."""
return self.top_w.keypress(self.top_w_size(size, *self.calculate_padding_filler(size, True)), key)
- def _get_focus(self) -> Widget:
+ @property
+ def focus(self) -> Widget:
"""
- Currently self.top_w is always the focus of an Overlay
+ Read-only property returning the child widget in focus for
+ container widgets. This default implementation
+ always returns ``None``, indicating that this widget has no children.
"""
return self.top_w
- focus = property(_get_focus,
- doc="the top widget in this overlay is always in focus")
- def _get_focus_position(self) -> Literal[1]:
+ @property
+ def focus_position(self) -> Literal[1]:
"""
Return the top widget position (currently always 1).
"""
return 1
- def _set_focus_position(self, position):
+ @focus_position.setter
+ def focus_position(self, position: int) -> None:
"""
Set the widget in focus. Currently only position 0 is accepted.
@@ -719,10 +898,30 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
"""
if position != 1:
raise IndexError(f"Overlay widget focus_position currently must always be set to 1, not {position}")
- focus_position = property(_get_focus_position, _set_focus_position,
- doc="index of child widget in focus, currently always 1")
- def _contents(self):
+ @property
+ def contents(self):
+ """
+ a list-like object similar to::
+
+ [(bottom_w, bottom_options)),
+ (top_w, top_options)]
+
+ This object may be used to read or update top and bottom widgets and
+ top widgets's options, but no widgets may be added or removed.
+
+ `top_options` takes the form
+ `(align_type, align_amount, width_type, width_amount, min_width, left,
+ right, valign_type, valign_amount, height_type, height_amount,
+ min_height, top, bottom)`
+
+ bottom_options is always
+ `('left', None, 'relative', 100, None, 0, 0,
+ 'top', None, 'relative', 100, None, 0, 0)`
+ which means that bottom widget always covers the full area of the Overlay.
+ writing a different value for `bottom_options` raises an
+ :exc:`OverlayError`.
+ """
class OverlayContents:
def __len__(inner_self):
return 2
@@ -730,6 +929,13 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
__setitem__ = self._contents__setitem__
return OverlayContents()
+ @contents.setter
+ def contents(self, new_contents):
+ if len(new_contents) != 2:
+ raise ValueError("Contents length for overlay should be only 2")
+ self.contents[0] = new_contents[0]
+ self.contents[1] = new_contents[1]
+
def _contents__getitem__(self, index: Literal[0, 1]):
if index == 0:
return (self.bottom_w, self._DEFAULT_BOTTOM_OPTIONS)
@@ -786,27 +992,6 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
else:
raise IndexError(f"Overlay.contents has no position {index!r}")
self._invalidate()
- contents = property(_contents, doc="""
- a list-like object similar to::
-
- [(bottom_w, bottom_options)),
- (top_w, top_options)]
-
- This object may be used to read or update top and bottom widgets and
- top widgets's options, but no widgets may be added or removed.
-
- `top_options` takes the form
- `(align_type, align_amount, width_type, width_amount, min_width, left,
- right, valign_type, valign_amount, height_type, height_amount,
- min_height, top, bottom)`
-
- bottom_options is always
- `('left', None, 'relative', 100, None, 0, 0,
- 'top', None, 'relative', 100, None, 0, 0)`
- which means that bottom widget always covers the full area of the Overlay.
- writing a different value for `bottom_options` raises an
- :exc:`OverlayError`.
- """)
def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:
"""Return cursor coords from top_w, if any."""
@@ -944,54 +1129,121 @@ class Frame(Widget, WidgetContainerMixin):
self._footer = footer
self.focus_part = focus_part
- def get_header(self) -> Widget | None:
+ @property
+ def header(self) -> Widget | None:
return self._header
- def set_header(self, header: Widget | None):
+ @header.setter
+ def header(self, header: Widget | None):
self._header = header
if header is None and self.focus_part == 'header':
self.focus_part = 'body'
self._invalidate()
- header = property(get_header, set_header)
- def get_body(self) -> Widget:
+ def get_header(self) -> Widget | None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}.get_header` is deprecated, "
+ f"standard property `{self.__class__.__name__}.header` should be used instead",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.header
+
+ def set_header(self, header: Widget | None):
+ warnings.warn(
+ f"method `{self.__class__.__name__}.set_header` is deprecated, "
+ f"standard property `{self.__class__.__name__}.header` should be used instead",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.header = header
+
+ @property
+ def body(self) -> Widget:
return self._body
- def set_body(self, body: Widget) -> None:
+
+ @body.setter
+ def body(self, body: Widget) -> None:
self._body = body
self._invalidate()
- body = property(get_body, set_body)
- def get_footer(self) -> Widget | None:
+ def get_body(self) -> Widget:
+ warnings.warn(
+ f"method `{self.__class__.__name__}.get_body` is deprecated, "
+ f"standard property {self.__class__.__name__}.body should be used instead",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.body
+
+ def set_body(self, body: Widget) -> None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}.set_body` is deprecated, "
+ f"standard property `{self.__class__.__name__}.body` should be used instead",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.body = body
+
+ @property
+ def footer(self) -> Widget | None:
return self._footer
- def set_footer(self, footer: Widget | None) -> None:
+ @footer.setter
+ def footer(self, footer: Widget | None) -> None:
self._footer = footer
if footer is None and self.focus_part == 'footer':
self.focus_part = 'body'
self._invalidate()
- footer = property(get_footer, set_footer)
- def set_focus(self, part: Literal['header', 'footer', 'body']) -> None:
+ def get_footer(self) -> Widget | None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}.get_footer` is deprecated, "
+ f"standard property `{self.__class__.__name__}.footer` should be used instead",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.footer
+
+ def set_footer(self, footer: Widget | None) -> None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}.set_footer` is deprecated, "
+ f"standard property `{self.__class__.__name__}.footer` should be used instead",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.footer = footer
+
+ @property
+ def focus_position(self) -> Literal['header', 'footer', 'body']:
"""
- Determine which part of the frame is in focus.
+ writeable property containing an indicator which part of the frame
+ that is in focus: `'body', 'header'` or `'footer'`.
- .. note:: included for backwards compatibility. You should rather use
- the container property :attr:`.focus_position` to set this value.
+ :returns: one of 'header', 'footer' or 'body'.
+ :rtype: str
+ """
+ return self.focus_part
+
+ @focus_position.setter
+ def focus_position(self, part: Literal['header', 'footer', 'body']) -> None:
+ """
+ Determine which part of the frame is in focus.
:param part: 'header', 'footer' or 'body'
:type part: str
"""
if part not in ('header', 'footer', 'body'):
raise IndexError(f'Invalid position for Frame: {part}')
- if (part == 'header' and self._header is None) or (
- part == 'footer' and self._footer is None):
+ if (part == 'header' and self._header is None) or (part == 'footer' and self._footer is None):
raise IndexError(f'This Frame has no {part}')
self.focus_part = part
self._invalidate()
def get_focus(self) -> Literal['header', 'footer', 'body']:
"""
- Return an indicator which part of the frame is in focus
+ writeable property containing an indicator which part of the frame
+ that is in focus: `'body', 'header'` or `'footer'`.
.. note:: included for backwards compatibility. You should rather use
the container property :attr:`.focus_position` to get this value.
@@ -999,24 +1251,54 @@ class Frame(Widget, WidgetContainerMixin):
:returns: one of 'header', 'footer' or 'body'.
:rtype: str
"""
- return self.focus_part
+ warnings.warn(
+ "included for backwards compatibility."
+ "You should rather use the container property `.focus_position` to get this value.",
+ PendingDeprecationWarning,
+ )
+ return self.focus_position
- def _get_focus(self) -> Widget:
+ def set_focus(self, part: Literal['header', 'footer', 'body']) -> None:
+ warnings.warn(
+ "included for backwards compatibility."
+ "You should rather use the container property `.focus_position` to set this value.",
+ PendingDeprecationWarning,
+ )
+ self.focus_position = part
+
+ @property
+ def focus(self) -> Widget:
+ """
+ child :class:`Widget` in focus: the body, header or footer widget.
+ This is a read-only property."""
return {
'header': self._header,
'footer': self._footer,
'body': self._body
}[self.focus_part]
- focus = property(_get_focus, doc="""
- child :class:`Widget` in focus: the body, header or footer widget.
- This is a read-only property.""")
- focus_position = property(get_focus, set_focus, doc="""
- writeable property containing an indicator which part of the frame
- that is in focus: `'body', 'header'` or `'footer'`.
- """)
+ @property
+ def contents(self):
+ """
+ a dict-like object similar to::
- def _contents(self):
+ {
+ 'body': (body_widget, None),
+ 'header': (header_widget, None), # if frame has a header
+ 'footer': (footer_widget, None) # if frame has a footer
+ }
+
+ This object may be used to read or update the contents of the Frame.
+
+ The values are similar to the list-like .contents objects used
+ in other containers with (:class:`Widget`, options) tuples, but are
+ constrained to keys for each of the three usual parts of a Frame.
+ When other keys are used a :exc:`KeyError` will be raised.
+
+ Currently all options are `None`, but using the :meth:`options` method
+ to create the options value is recommended for forwards
+ compatibility.
+ """
class FrameContents:
def __len__(inner_self):
return len(inner_self.keys())
@@ -1080,33 +1362,21 @@ class Frame(Widget, WidgetContainerMixin):
def _contents__delitem__(self, key: Literal['header', 'footer', 'body']):
if key not in ('header', 'footer'):
raise KeyError(f"Frame.contents can't remove key: {key!r}")
- if (key == 'header' and self._header is None
- ) or (key == 'footer' and self._footer is None):
+ if (key == 'header' and self._header is None) or (key == 'footer' and self._footer is None):
raise KeyError(f"Frame.contents has no key: {key!r}")
if key == 'header':
self.header = None
else:
self.footer = None
- contents = property(_contents, doc="""
- a dict-like object similar to::
-
- {
- 'body': (body_widget, None),
- 'header': (header_widget, None), # if frame has a header
- 'footer': (footer_widget, None) # if frame has a footer
- }
-
- This object may be used to read or update the contents of the Frame.
-
- The values are similar to the list-like .contents objects used
- in other containers with (:class:`Widget`, options) tuples, but are
- constrained to keys for each of the three usual parts of a Frame.
- When other keys are used a :exc:`KeyError` will be raised.
- Currently all options are `None`, but using the :meth:`options` method
- to create the options value is recommended for forwards
- compatibility.
- """)
+ def _contents(self):
+ warnings.warn(
+ f"method `{self.__class__.__name__}._contents` is deprecated, "
+ f"please use property `{self.__class__.__name__}.contents`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.contents
def options(self) -> None:
"""
@@ -1238,19 +1508,18 @@ class Frame(Widget, WidgetContainerMixin):
return key
return self.body.keypress( (maxcol, remaining), key )
- def mouse_event(self, size: tuple[str, str], event, button: int, col: int, row: int, focus: bool) -> bool | None:
+ def mouse_event(self, size: tuple[int, int], event, button: int, col: int, row: int, focus: bool) -> bool | None:
"""
Pass mouse event to appropriate part of frame.
Focus may be changed on button 1 press.
"""
(maxcol, maxrow) = size
- (htrim, ftrim),(hrows, frows) = self.frame_top_bottom((maxcol, maxrow), focus)
+ (htrim, ftrim), (hrows, frows) = self.frame_top_bottom((maxcol, maxrow), focus)
if row < htrim: # within header
focus = focus and self.focus_part == 'header'
- if is_mouse_press(event) and button==1:
- if self.header.selectable():
- self.set_focus('header')
+ if is_mouse_press(event) and button == 1 and self.header.selectable():
+ self.focus_position = 'header'
if not hasattr(self.header, 'mouse_event'):
return False
return self.header.mouse_event( (maxcol,), event,
@@ -1258,9 +1527,8 @@ class Frame(Widget, WidgetContainerMixin):
if row >= maxrow-ftrim: # within footer
focus = focus and self.focus_part == 'footer'
- if is_mouse_press(event) and button==1:
- if self.footer.selectable():
- self.set_focus('footer')
+ if is_mouse_press(event) and button == 1 and self.footer.selectable():
+ self.focus_position = 'footer'
if not hasattr(self.footer, 'mouse_event'):
return False
return self.footer.mouse_event( (maxcol,), event,
@@ -1270,7 +1538,7 @@ class Frame(Widget, WidgetContainerMixin):
focus = focus and self.focus_part == 'body'
if is_mouse_press(event) and button==1:
if self.body.selectable():
- self.set_focus('body')
+ self.focus_position = 'body'
if not hasattr(self.body, 'mouse_event'):
return False
@@ -1391,7 +1659,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
focus_item = i
if self.contents and focus_item is not None:
- self.set_focus(focus_item)
+ self.focus_position = focus_item
self.pref_col = 0
@@ -1412,16 +1680,29 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
except (TypeError, ValueError):
raise PileError(f"added content invalid: {item!r}")
- def _get_widget_list(self):
+ @property
+ def widget_list(self):
+ """
+ A list of the widgets in this Pile
+
+ .. note:: only for backwards compatibility. You should use the new
+ standard container property :attr:`contents`.
+ """
+ warnings.warn(
+ "only for backwards compatibility. You should use the new standard container property `contents`",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
ml = MonitoredList(w for w, t in self.contents)
def user_modified():
- self._set_widget_list(ml)
+ self.widget_list = ml
ml.set_modified_callback(user_modified)
return ml
- def _set_widget_list(self, widgets):
+ @widget_list.setter
+ def widget_list(self, widgets):
focus_position = self.focus_position
self.contents = [
(new, options) for (new, (w, options)) in zip(widgets,
@@ -1429,25 +1710,37 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
chain(self.contents, repeat((None, (WEIGHT, 1)))))]
if focus_position < len(widgets):
self.focus_position = focus_position
- widget_list = property(_get_widget_list, _set_widget_list, doc="""
- A list of the widgets in this Pile
+
+ @property
+ def item_types(self):
+ """
+ A list of the options values for widgets in this Pile.
.. note:: only for backwards compatibility. You should use the new
standard container property :attr:`contents`.
- """)
-
- def _get_item_types(self):
+ """
+ warnings.warn(
+ "only for backwards compatibility. You should use the new standard container property `contents`",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
ml = MonitoredList(
# return the old item type names
({GIVEN: FIXED, PACK: FLOW}.get(f, f), height)
for w, (f, height) in self.contents)
def user_modified():
- self._set_item_types(ml)
+ self.item_types = ml
ml.set_modified_callback(user_modified)
return ml
- def _set_item_types(self, item_types):
+ @item_types.setter
+ def item_types(self, item_types):
+ warnings.warn(
+ "only for backwards compatibility. You should use the new standard container property `contents`",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
focus_position = self.focus_position
self.contents = [
(w, ({FIXED: GIVEN, FLOW: PACK}.get(new_t, new_t), new_height))
@@ -1455,18 +1748,10 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
in zip(item_types, self.contents)]
if focus_position < len(item_types):
self.focus_position = focus_position
- item_types = property(_get_item_types, _set_item_types, doc="""
- A list of the options values for widgets in this Pile.
-
- .. note:: only for backwards compatibility. You should use the new
- standard container property :attr:`contents`.
- """)
- def _get_contents(self):
- return self._contents
- def _set_contents(self, c):
- self._contents[:] = c
- contents = property(_get_contents, _set_contents, doc="""
+ @property
+ def contents(self):
+ """
The contents of this Pile as a list of (widget, options) tuples.
options currently may be one of
@@ -1491,7 +1776,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
will updated automatically.
.. seealso:: Create new options tuples with the :meth:`options` method
- """)
+ """
+ return self._contents
+
+ @contents.setter
+ def contents(self, c):
+ self._contents[:] = c
@staticmethod
def options(
@@ -1512,7 +1802,15 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
raise PileError(f'invalid height_type: {height_type!r}')
return (height_type, height_amount)
- def set_focus(self, item: Widget | int) -> None:
+ @property
+ def focus(self) -> Widget | None:
+ """the child widget in focus or None when Pile is empty"""
+ if not self.contents:
+ return None
+ return self.contents[self.focus_position][0]
+
+ @focus.setter
+ def focus(self, item: Widget | int) -> None:
"""
Set the item in focus, for backwards compatibility.
@@ -1524,7 +1822,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
:type item: Widget or int
"""
if isinstance(item, int):
- return self._set_focus_position(item)
+ self.focus_position = item
+ return
for i, (w, options) in enumerate(self.contents):
if item == w:
self.focus_position = i
@@ -1537,32 +1836,57 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
also use the new standard container property .focus to get the
child widget in focus.
"""
- if not self.contents:
- return None
- return self.contents[self.focus_position][0]
- focus = property(get_focus,
- doc="the child widget in focus or None when Pile is empty")
-
- focus_item = property(get_focus, set_focus, doc="""
- A property for reading and setting the widget in focus.
-
- .. note::
+ warnings.warn(
+ "for backwards compatibility."
+ "You may also use the new standard container property .focus to get the child widget in focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus
- only for backwards compatibility. You should use the new
- standard container properties :attr:`focus` and
- :attr:`focus_position` to get the child widget in focus or modify the
- focus position.
- """)
+ def set_focus(self, item: Widget | int) -> None:
+ warnings.warn(
+ "for backwards compatibility."
+ "You may also use the new standard container property .focus to get the child widget in focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.focus = item
+
+ @property
+ def focus_item(self):
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container properties "
+ "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus
+
+ @focus_item.setter
+ def focus_item(self, new_item):
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container properties "
+ "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.focus = new_item
- def _get_focus_position(self) -> int:
+ @property
+ def focus_position(self) -> int:
"""
- Return the index of the widget in focus or None if this Pile is
- empty.
+ index of child widget in focus.
+ Raises :exc:`IndexError` if read when Pile is empty, or when set to an invalid index.
"""
if not self.contents:
raise IndexError("No focus_position, Pile is empty")
return self.contents.focus
- def _set_focus_position(self, position: int) -> None:
+
+ @focus_position.setter
+ def focus_position(self, position: int) -> None:
"""
Set the widget in focus.
@@ -1574,10 +1898,6 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
except (TypeError, IndexError):
raise IndexError(f"No Pile child widget at position {position}")
self.contents.focus = position
- focus_position = property(_get_focus_position, _set_focus_position, doc="""
- index of child widget in focus. Raises :exc:`IndexError` if read when
- Pile is empty, or when set to an invalid index.
- """)
def get_pref_col(self, size):
"""Return the preferred column for the cursor, or None."""
@@ -1625,7 +1945,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
if f == GIVEN:
l.append(height)
else:
- l.append(w.rows((maxcol,), focus=focus and self.focus_item == w))
+ l.append(w.rows((maxcol,), focus=focus and self.focus == w))
return l
# pile is a box widget
@@ -1633,7 +1953,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
wtotal = 0
for w, (f, height) in self.contents:
if f == PACK:
- rows = w.rows((maxcol,), focus=focus and self.focus_item == w)
+ rows = w.rows((maxcol,), focus=focus and self.focus == w)
l.append(rows)
remaining -= rows
elif f == GIVEN:
@@ -1666,7 +1986,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
combinelist = []
for i, (w, (f, height)) in enumerate(self.contents):
- item_focus = self.focus_item == w
+ item_focus = self.focus == w
canv = None
if f == GIVEN:
canv = w.render((maxcol, height), focus=focus and item_focus)
@@ -1694,7 +2014,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
"""Return the cursor coordinates of the focus widget."""
if not self.selectable():
return None
- if not hasattr(self.focus_item, 'get_cursor_coords'):
+ if not hasattr(self.focus, 'get_cursor_coords'):
return None
i = self.focus_position
@@ -1708,9 +2028,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
if item_rows is None:
item_rows = self.get_item_rows(size, focus=True)
maxrow = item_rows[i]
- coords = self.focus_item.get_cursor_coords((maxcol, maxrow))
+ coords = self.focus.get_cursor_coords((maxcol, maxrow))
else:
- coords = self.focus_item.get_cursor_coords((maxcol,))
+ coords = self.focus.get_cursor_coords((maxcol,))
if coords is None:
return None
@@ -1766,8 +2086,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
rowlist = list(range(rows))
for row in rowlist:
tsize = self.get_item_size(size, j, True, item_rows)
- if self.focus_item.move_cursor_to_coords(
- tsize, self.pref_col, row):
+ if self.focus.move_cursor_to_coords(tsize, self.pref_col, row):
break
return
@@ -1835,7 +2154,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
else:
return False
- focus = focus and self.focus_item == w
+ focus = focus and self.focus == w
if is_mouse_press(event) and button == 1:
if w.selectable():
self.focus_position = i
@@ -1956,14 +2275,33 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
except (TypeError, ValueError):
raise ColumnsError(f"added content invalid {item!r}")
- def _get_widget_list(self) -> MonitoredList:
+ @property
+ def widget_list(self) -> MonitoredList:
+ """
+ A list of the widgets in this Columns
+
+ .. note:: only for backwards compatibility. You should use the new
+ standard container property :attr:`contents`.
+ """
+ warnings.warn(
+ "only for backwards compatibility. You should use the new standard container `contents`",
+ PendingDeprecationWarning,
+ stacklevel=2
+ )
ml = MonitoredList(w for w, t in self.contents)
+
def user_modified():
- self._set_widget_list(ml)
+ self.widget_list = ml
ml.set_modified_callback(user_modified)
return ml
- def _set_widget_list(self, widgets):
+ @widget_list.setter
+ def widget_list(self, widgets):
+ warnings.warn(
+ "only for backwards compatibility. You should use the new standard container `contents`",
+ PendingDeprecationWarning,
+ stacklevel=2
+ )
focus_position = self.focus_position
self.contents = [
# need to grow contents list if widgets is longer
@@ -1974,24 +2312,38 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
]
if focus_position < len(widgets):
self.focus_position = focus_position
- widget_list = property(_get_widget_list, _set_widget_list, doc="""
- A list of the widgets in this Columns
- .. note:: only for backwards compatibility. You should use the new
- standard container property :attr:`contents`.
- """)
-
- def _get_column_types(self) -> MonitoredList:
+ @property
+ def column_types(self) -> MonitoredList:
+ """
+ A list of the old partial options values for widgets in this Pile,
+ for backwards compatibility only. You should use the new standard
+ container property .contents to modify Pile contents.
+ """
+ warnings.warn(
+ "for backwards compatibility only."
+ "You should use the new standard container property .contents to modify Pile contents.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
ml = MonitoredList(
# return the old column type names
({GIVEN: FIXED, PACK: FLOW}.get(t, t), n)
for w, (t, n, b) in self.contents)
+
def user_modified():
- self._set_column_types(ml)
+ self.column_types = ml
ml.set_modified_callback(user_modified)
return ml
- def _set_column_types(self, column_types):
+ @column_types.setter
+ def column_types(self, column_types):
+ warnings.warn(
+ "for backwards compatibility only."
+ "You should use the new standard container property .contents to modify Pile contents.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
focus_position = self.focus_position
self.contents = [
(w, ({FIXED: GIVEN, FLOW: PACK}.get(new_t, new_t), new_n, b))
@@ -2000,57 +2352,69 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
]
if focus_position < len(column_types):
self.focus_position = focus_position
- column_types = property(_get_column_types, _set_column_types, doc="""
- A list of the old partial options values for widgets in this Pile,
- for backwards compatibility only. You should use the new standard
- container property .contents to modify Pile contents.
- """)
- def _get_box_columns(self) -> MonitoredList:
+ @property
+ def box_columns(self) -> MonitoredList:
+ """
+ A list of the indexes of the columns that are to be treated as
+ box widgets when the Columns is treated as a flow widget.
+
+ .. note:: only for backwards compatibility. You should use the new
+ standard container property :attr:`contents`.
+ """
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container property `contents`",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
ml = MonitoredList(
i for i, (w, (t, n, b)) in enumerate(self.contents) if b)
+
def user_modified():
- self._set_box_columns(ml)
+ self.box_columns = ml
ml.set_modified_callback(user_modified)
return ml
- def _set_box_columns(self, box_columns):
+ @box_columns.setter
+ def box_columns(self, box_columns):
+ warnings.warn(
+ "only for backwards compatibility."
+ "You should use the new standard container property `contents`",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
box_columns = set(box_columns)
self.contents = [
(w, (t, n, i in box_columns))
for (i, (w, (t, n, b))) in enumerate(self.contents)]
- box_columns = property(_get_box_columns, _set_box_columns, doc="""
- A list of the indexes of the columns that are to be treated as
- box widgets when the Columns is treated as a flow widget.
- .. note:: only for backwards compatibility. You should use the new
- standard container property :attr:`contents`.
- """)
-
- def _get_has_pack_type(self) -> bool:
- import warnings
+ @property
+ def has_flow_type(self) -> bool:
+ """
+ .. deprecated:: 1.0 Read values from :attr:`contents` instead.
+ """
warnings.warn(".has_flow_type is deprecated, read values from .contents instead.", DeprecationWarning)
return PACK in self.column_types
- def _set_has_pack_type(self, value):
- import warnings
+ @has_flow_type.setter
+ def has_flow_type(self, value):
warnings.warn(".has_flow_type is deprecated, read values from .contents instead.", DeprecationWarning)
- has_flow_type = property(_get_has_pack_type, _set_has_pack_type, doc="""
- .. deprecated:: 1.0 Read values from :attr:`contents` instead.
- """)
- def _get_contents(self):
- return self._contents
-
- def _set_contents(self, c):
- self._contents[:] = c
- contents = property(_get_contents, _set_contents, doc="""
+ @property
+ def contents(self):
+ """
The contents of this Columns as a list of `(widget, options)` tuples.
This list may be modified like a normal list and the Columns
widget will update automatically.
.. seealso:: Create new options tuples with the :meth:`options` method
- """)
+ """
+ return self._contents
+
+ @contents.setter
+ def contents(self, c):
+ self._contents[:] = c
@staticmethod
def options(
@@ -2099,7 +2463,13 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
.. note:: only for backwards compatibility. You may also use the new
standard container property :attr:`focus_position` to set the focus.
"""
- self._set_focus_position(num)
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus_position`",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.focus_position = num
def get_focus_column(self) -> int:
"""
@@ -2108,6 +2478,12 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
.. note:: only for backwards compatibility. You may also use the new
standard container property :attr:`focus_position` to get the focus.
"""
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus_position`",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
return self.focus_position
def set_focus(self, item: Widget | int) -> None:
@@ -2118,16 +2494,26 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
standard container property :attr:`focus_position` to get the focus.
:param item: widget or integer index"""
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus_position` to get the focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
if isinstance(item, int):
- return self._set_focus_position(item)
+ self.focus_position = item
+ return
for i, (w, options) in enumerate(self.contents):
if item == w:
self.focus_position = i
return
raise ValueError(f"Widget not found in Columns contents: {item!r}")
- def get_focus(self) -> Widget | None:
+ @property
+ def focus(self) -> Widget | None:
"""
+ the child widget in focus or None when Columns is empty
+
Return the widget in focus, for backwards compatibility. You may
also use the new standard container property .focus to get the
child widget in focus.
@@ -2135,19 +2521,34 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
if not self.contents:
return None
return self.contents[self.focus_position][0]
- focus = property(get_focus,
- doc="the child widget in focus or None when Columns is empty")
- def _get_focus_position(self) -> int | None:
+ def get_focus(self):
"""
- Return the index of the widget in focus or None if this Columns is
- empty.
+ Return the widget in focus, for backwards compatibility.
+
+ .. note:: only for backwards compatibility. You may also use the new
+ standard container property :attr:`focus` to get the focus.
+ """
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus` to get the focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus
+
+ @property
+ def focus_position(self) -> int | None:
+ """
+ index of child widget in focus.
+ Raises :exc:`IndexError` if read when Columns is empty, or when set to an invalid index.
"""
if not self.widget_list:
raise IndexError("No focus_position, Columns is empty")
return self.contents.focus
- def _set_focus_position(self, position: int) -> None:
+ @focus_position.setter
+ def focus_position(self, position: int) -> None:
"""
Set the widget in focus.
@@ -2159,18 +2560,33 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
except (TypeError, IndexError):
raise IndexError(f"No Columns child widget at position {position}")
self.contents.focus = position
- focus_position = property(_get_focus_position, _set_focus_position, doc="""
- index of child widget in focus. Raises :exc:`IndexError` if read when
- Columns is empty, or when set to an invalid index.
- """)
- focus_col = property(_get_focus_position, _set_focus_position, doc="""
+ @property
+ def focus_col(self):
+ """
A property for reading and setting the index of the column in
focus.
.. note:: only for backwards compatibility. You may also use the new
standard container property :attr:`focus_position` to get the focus.
- """)
+ """
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus_position` to get the focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus_position
+
+ @focus_col.setter
+ def focus_col(self, new_position) -> None:
+ warnings.warn(
+ "only for backwards compatibility."
+ "You may also use the new standard container property `focus_position` to get the focus.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.focus_position = new_position
def column_widths(self, size: tuple[int] | tuple[int, int], focus: bool = False) -> list[int]:
"""
@@ -2370,14 +2786,14 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
for i, (width, (w, (t, n, b))) in enumerate(zip(widths, self.contents)):
if col < x:
return False
- w = self.widget_list[i]
+ w = self.contents[i][0]
end = x + width
if col >= end:
x = end + self.dividechars
continue
- focus = focus and self.focus_col == i
+ focus = focus and self.focus_position == i
if is_mouse_press(event) and button == 1 and w.selectable():
self.focus_position = i
@@ -2404,7 +2820,7 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
else:
col = w.get_pref_col((cwidth,) + size[1:])
if isinstance(col, int):
- col += self.focus_col * self.dividechars
+ col += self.focus_position * self.dividechars
col += sum(widths[:self.focus_position])
if col is None:
col = self.pref_col
diff --git a/urwid/decoration.py b/urwid/decoration.py
index f1ab16c..ff7a5a0 100755
--- a/urwid/decoration.py
+++ b/urwid/decoration.py
@@ -23,6 +23,7 @@
from __future__ import annotations
import typing
+import warnings
from collections.abc import Hashable, Mapping
from urwid.canvas import CompositeCanvas, SolidCanvas
@@ -77,15 +78,35 @@ class WidgetDecoration(Widget): # "decorator" was already taken
def _repr_words(self):
return super()._repr_words() + [repr(self._original_widget)]
- def _get_original_widget(self) -> Widget:
+ @property
+ def original_widget(self) -> Widget:
return self._original_widget
- def _set_original_widget(self, original_widget):
+ @original_widget.setter
+ def original_widget(self, original_widget: Widget) -> None:
self._original_widget = original_widget
self._invalidate()
- original_widget = property(_get_original_widget, _set_original_widget)
- def _get_base_widget(self):
+ def _get_original_widget(self) -> Widget:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_original_widget` is deprecated, "
+ f"please use property `{self.__class__.__name__}.original_widget`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.original_widget
+
+ def _set_original_widget(self, original_widget):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_original_widget` is deprecated, "
+ f"please use property `{self.__class__.__name__}.original_widget`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.original_widget = original_widget
+
+ @property
+ def base_widget(self) -> Widget:
"""
Return the widget without decorations. If there is only one
Decoration then this is the same as original_widget.
@@ -104,7 +125,14 @@ class WidgetDecoration(Widget): # "decorator" was already taken
w = w._original_widget
return w
- base_widget = property(_get_base_widget)
+ def _get_base_widget(self):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_base_widget` is deprecated, "
+ f"please use property `{self.__class__.__name__}.base_widget`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.base_widget
def selectable(self) -> bool:
return self._original_widget.selectable()
@@ -299,10 +327,40 @@ class AttrWrap(AttrMap):
d['focus_attr'] = self.focus_attr
return d
- # backwards compatibility, widget used to be stored as w
- get_w = WidgetDecoration._get_original_widget
- set_w = WidgetDecoration._set_original_widget
- w = property(get_w, set_w)
+ @property
+ def w(self) -> Widget:
+ """backwards compatibility, widget used to be stored as w"""
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as w",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.original_widget
+
+ @w.setter
+ def w(self, new_widget: Widget) -> None:
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as w",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.original_widget = new_widget
+
+ def get_w(self):
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as w",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.original_widget
+
+ def set_w(self, new_widget: Widget) -> None:
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as w",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.original_widget = new_widget
def get_attr(self):
return self.attr_map[None]
@@ -387,7 +445,23 @@ class BoxAdapter(WidgetDecoration):
return dict(super()._repr_attrs(), height=self.height)
# originally stored as box_widget, keep for compatibility
- box_widget = property(WidgetDecoration._get_original_widget, WidgetDecoration._set_original_widget)
+ @property
+ def box_widget(self) -> Widget:
+ warnings.warn(
+ "originally stored as box_widget, keep for compatibility",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.original_widget
+
+ @box_widget.setter
+ def box_widget(self, widget: Widget):
+ warnings.warn(
+ "originally stored as box_widget, keep for compatibility",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.original_widget = widget
def sizing(self):
return {FLOW}
@@ -571,33 +645,71 @@ class Padding(WidgetDecoration):
min_width=self.min_width)
return remove_defaults(attrs, Padding.__init__)
- def _get_align(self) -> Literal['left', 'center', 'right'] | tuple[Literal['relative'], int]:
+ @property
+ def align(self) -> Literal['left', 'center', 'right'] | tuple[Literal['relative'], int]:
"""
Return the padding alignment setting.
"""
return simplify_align(self._align_type, self._align_amount)
- def _set_align(self, align: Literal['left', 'center', 'right'] | tuple[Literal['relative'], int]) -> None:
+ @align.setter
+ def align(self, align: Literal['left', 'center', 'right'] | tuple[Literal['relative'], int]) -> None:
"""
Set the padding alignment.
"""
self._align_type, self._align_amount = normalize_align(align, PaddingError)
self._invalidate()
- align = property(_get_align, _set_align)
- def _get_width(self) -> Literal['clip', 'pack'] | int | tuple[Literal['relative'], int]:
+ def _get_align(self) -> Literal['left', 'center', 'right'] | tuple[Literal['relative'], int]:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_align` is deprecated, "
+ f"please use property `{self.__class__.__name__}.align`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.align
+
+ def _set_align(self, align: Literal['left', 'center', 'right'] | tuple[Literal['relative'], int]) -> None:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_align` is deprecated, "
+ f"please use property `{self.__class__.__name__}.align`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.align = align
+
+ @property
+ def width(self) -> Literal['clip', 'pack'] | int | tuple[Literal['relative'], int]:
"""
Return the padding width.
"""
return simplify_width(self._width_type, self._width_amount)
- def _set_width(self, width: Literal['clip', 'pack'] | int | tuple[Literal['relative'], int]) -> None:
+ @width.setter
+ def width(self, width: Literal['clip', 'pack'] | int | tuple[Literal['relative'], int]) -> None:
"""
Set the padding width.
"""
self._width_type, self._width_amount = normalize_width(width, PaddingError)
self._invalidate()
- width = property(_get_width, _set_width)
+
+ def _get_width(self) -> Literal['clip', 'pack'] | int | tuple[Literal['relative'], int]:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_width` is deprecated, "
+ f"please use property `{self.__class__.__name__}.width`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.width
+
+ def _set_width(self, width: Literal['clip', 'pack'] | int | tuple[Literal['relative'], int]) -> None:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_width` is deprecated, "
+ f"please use property `{self.__class__.__name__}.width`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.width = width
def render(
self,
@@ -847,10 +959,41 @@ class Filler(WidgetDecoration):
min_height=self.min_height)
return remove_defaults(attrs, Filler.__init__)
- # backwards compatibility, widget used to be stored as body
- get_body = WidgetDecoration._get_original_widget
- set_body = WidgetDecoration._set_original_widget
- body = property(get_body, set_body)
+ @property
+ def body(self):
+ """backwards compatibility, widget used to be stored as body"""
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as body",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return self.original_widget
+
+ @body.setter
+ def body(self, new_body):
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as body",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ self.original_widget = new_body
+
+ def get_body(self):
+ """backwards compatibility, widget used to be stored as body"""
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as body",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.original_widget
+
+ def set_body(self, new_body):
+ warnings.warn(
+ "backwards compatibility, widget used to be stored as body",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.original_widget = new_body
def selectable(self) -> bool:
"""Return selectable from body."""
diff --git a/urwid/display_common.py b/urwid/display_common.py
index a4636fe..30ece96 100755
--- a/urwid/display_common.py
+++ b/urwid/display_common.py
@@ -610,7 +610,8 @@ class AttrSpec:
def strikethrough(self) -> bool:
return self._value & _STRIKETHROUGH != 0
- def _colors(self) -> int:
+ @property
+ def colors(self) -> int:
"""
Return the maximum colors required for this object.
@@ -625,7 +626,15 @@ class AttrSpec:
if self._value & (_BG_BASIC_COLOR | _FG_BASIC_COLOR):
return 16
return 1
- colors = property(_colors)
+
+ def _colors(self) -> int:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._colors` is deprecated, "
+ f"please use property `{self.__class__.__name__}.colors`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.colors
def __repr__(self) -> str:
"""
@@ -650,7 +659,8 @@ class AttrSpec:
return _color_desc_true(self.foreground_number)
return _color_desc_256(self.foreground_number)
- def _foreground(self) -> str:
+ @property
+ def foreground(self) -> str:
return (
self._foreground_color()
+ ',bold' * self.bold
@@ -661,7 +671,8 @@ class AttrSpec:
+ ',strikethrough' * self.strikethrough
)
- def _set_foreground(self, foreground: str) -> None:
+ @foreground.setter
+ def foreground(self, foreground: str) -> None:
color = None
flags = 0
# handle comma-separated foreground
@@ -699,9 +710,26 @@ class AttrSpec:
color = 0
self._value = (self._value & ~_FG_MASK) | color | flags
- foreground = property(_foreground, _set_foreground)
+ def _foreground(self) -> str:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._foreground` is deprecated, "
+ f"please use property `{self.__class__.__name__}.foreground`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.foreground
- def _background(self) -> str:
+ def _set_foreground(self, foreground: str) -> None:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_foreground` is deprecated, "
+ f"please use property `{self.__class__.__name__}.foreground`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.foreground = foreground
+
+ @property
+ def background(self) -> str:
"""Return the background color."""
if not (self.background_basic or self.background_high or self.background_true):
return 'default'
@@ -713,7 +741,8 @@ class AttrSpec:
return _color_desc_true(self.background_number)
return _color_desc_256(self.background_number)
- def _set_background(self, background: str) -> None:
+ @background.setter
+ def background(self, background: str) -> None:
flags = 0
if background in ('', 'default'):
color = 0
@@ -733,7 +762,23 @@ class AttrSpec:
raise AttrSpecError(f"Unrecognised color specification in background ({background!r})")
self._value = (self._value & ~_BG_MASK) | (color << _BG_SHIFT) | flags
- background = property(_background, _set_background)
+ def _background(self) -> str:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._background` is deprecated, "
+ f"please use property `{self.__class__.__name__}.background`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.background
+
+ def _set_background(self, background: str) -> None:
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_background` is deprecated, "
+ f"please use property `{self.__class__.__name__}.background`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.background = background
def get_rgb_values(self):
"""
diff --git a/urwid/graphics.py b/urwid/graphics.py
index b3356a4..310b9a5 100755
--- a/urwid/graphics.py
+++ b/urwid/graphics.py
@@ -23,6 +23,7 @@
from __future__ import annotations
import typing
+import warnings
from urwid.canvas import CanvasCombine, CanvasJoin, CompositeCanvas, SolidCanvas, TextCanvas
from urwid.container import Columns, Pile
@@ -942,13 +943,26 @@ class ProgressBar(Widget):
self._invalidate()
current = property(lambda self: self._current, set_completion)
- def _set_done(self, done):
+ @property
+ def done(self):
+ return self._done
+
+ @done.setter
+ def done(self, done):
"""
done -- progress amount at 100%
"""
self._done = done
self._invalidate()
- done = property(lambda self: self._done, _set_done)
+
+ def _set_done(self, done):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_done` is deprecated, "
+ f"please use property `{self.__class__.__name__}.done`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.done = done
def rows(self, size, focus: bool = False) -> int:
return 1
diff --git a/urwid/listbox.py b/urwid/listbox.py
index d4dddc4..a8cd91c 100644
--- a/urwid/listbox.py
+++ b/urwid/listbox.py
@@ -23,6 +23,7 @@
from __future__ import annotations
import typing
+import warnings
from collections.abc import MutableSequence
from urwid import signals
@@ -118,14 +119,23 @@ class SimpleListWalker(MonitoredList, ListWalker):
self.focus = 0
self.wrap_around = wrap_around
- def _get_contents(self):
+ @property
+ def contents(self):
"""
Return self.
Provides compatibility with old SimpleListWalker class.
"""
return self
- contents = property(_get_contents)
+
+ def _get_contents(self):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_contents` is deprecated, "
+ f"please use property`{self.__class__.__name__}.contents`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.contents
def _modified(self):
if self.focus >= len(self):
@@ -257,14 +267,14 @@ class ListBox(Widget, WidgetContainerMixin):
_selectable = True
_sizing = frozenset([BOX])
- def __init__(self, body):
+ def __init__(self, body: ListWalker):
"""
:param body: a ListWalker subclass such as
:class:`SimpleFocusListWalker` that contains
widgets to be displayed inside the list box
:type body: ListWalker
"""
- self._set_body(body)
+ self.body = body
# offset_rows is the number of rows between the top of the view
# and the top of the focused item
@@ -284,10 +294,16 @@ class ListBox(Widget, WidgetContainerMixin):
# variable for delayed valign change used by set_focus_valign
self.set_focus_valign_pending = None
- def _get_body(self):
+ @property
+ def body(self):
+ """
+ a ListWalker subclass such as :class:`SimpleFocusListWalker` that contains
+ widgets to be displayed inside the list box
+ """
return self._body
- def _set_body(self, body):
+ @body.setter
+ def body(self, body):
try:
disconnect_signal(self._body, "modified", self._invalidate)
except AttributeError:
@@ -306,10 +322,23 @@ class ListBox(Widget, WidgetContainerMixin):
self.render = nocache_widget_render_instance(self)
self._invalidate()
- body = property(_get_body, _set_body, doc="""
- a ListWalker subclass such as :class:`SimpleFocusListWalker` that contains
- widgets to be displayed inside the list box
- """)
+ def _get_body(self):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._get_body` is deprecated, "
+ f"please use property `{self.__class__.__name__}.body`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.body
+
+ def _set_body(self, body):
+ warnings.warn(
+ f"Method `{self.__class__.__name__}._set_body` is deprecated, "
+ f"please use property `{self.__class__.__name__}.body`",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.body = body
def calculate_visible(self, size: tuple[int, int], focus: bool = False):
"""
@@ -591,13 +620,14 @@ class ListBox(Widget, WidgetContainerMixin):
"""
return self._body.get_focus()
- def _get_focus(self):
+ @property
+ def focus(self):
"""
+ the child widget in focus or None when ListBox is empty.
+
Return the widget in focus according to our :obj:`list walker <ListWalker>`.
"""
return self._body.get_focus()[0]
- focus = property(_get_focus,
- doc="the child widget in focus or None when ListBox is empty")
def _get_focus_position(self):
"""
diff --git a/urwid/main_loop.py b/urwid/main_loop.py
index 7f93f2e..5cc7514 100755
--- a/urwid/main_loop.py
+++ b/urwid/main_loop.py
@@ -31,6 +31,7 @@ import signal
import sys
import time
import typing
+import warnings
from collections.abc import Callable, Iterable
from functools import wraps
from itertools import count
@@ -154,25 +155,51 @@ class MainLoop:
self._watch_pipes = {}
- def _set_widget(self, widget):
+ @property
+ def widget(self) -> Widget:
+ """
+ Property for the topmost widget used to draw the screen.
+ This must be a box widget.
+ """
+ return self._widget
+
+ @widget.setter
+ def widget(self, widget: Widget) -> None:
self._widget = widget
if self.pop_ups:
self._topmost_widget.original_widget = self._widget
else:
self._topmost_widget = self._widget
- widget = property(lambda self:self._widget, _set_widget, doc=
- """
- Property for the topmost widget used to draw the screen.
- This must be a box widget.
- """)
- def _set_pop_ups(self, pop_ups):
+ def _set_widget(self, widget: Widget) -> None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._set_widget` is deprecated, "
+ f"please use `{self.__class__.__name__}.widget` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.widget = widget
+
+ @property
+ def pop_ups(self):
+ return self._pop_ups
+
+ @pop_ups.setter
+ def pop_ups(self, pop_ups) -> None:
self._pop_ups = pop_ups
if pop_ups:
self._topmost_widget = PopUpTarget(self._widget)
else:
self._topmost_widget = self._widget
- pop_ups = property(lambda self:self._pop_ups, _set_pop_ups)
+
+ def _set_pop_ups(self, pop_ups) -> None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._set_pop_ups` is deprecated, "
+ f"please use `{self.__class__.__name__}.pop_ups` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.pop_ups = pop_ups
def set_alarm_in(self, sec, callback, user_data=None):
"""
@@ -1039,7 +1066,6 @@ class GLibEventLoop(EventLoop):
except ExitMainLoop:
self._loop.quit()
except:
- import sys
self._exc_info = sys.exc_info()
if self._loop.is_running():
self._loop.quit()
@@ -1377,7 +1403,6 @@ class TwistedEventLoop(EventLoop):
if self.manage_reactor:
self.reactor.stop()
except:
- import sys
print(sys.exc_info())
self._exc_info = sys.exc_info()
if self.manage_reactor:
diff --git a/urwid/monitored_list.py b/urwid/monitored_list.py
index 3d6d6fb..1a91495 100755
--- a/urwid/monitored_list.py
+++ b/urwid/monitored_list.py
@@ -23,6 +23,7 @@ from __future__ import annotations
import functools
import typing
+import warnings
from collections.abc import Callable
if typing.TYPE_CHECKING:
@@ -131,20 +132,26 @@ class MonitoredFocusList(MonitoredList):
def __repr__(self):
return f"{self.__class__.__name__}({list(self)!r}, focus={self.focus!r})"
- def _get_focus(self) -> int | None:
+ @property
+ def focus(self) -> int | None:
"""
+ Get/set the focus index. This value is read as None when the list
+ is empty, and may only be set to a value between 0 and len(self)-1
+ or an IndexError will be raised.
+
Return the index of the item "in focus" or None if
the list is empty.
- >>> MonitoredFocusList([1,2,3], focus=2)._get_focus()
+ >>> MonitoredFocusList([1,2,3], focus=2).focus
2
- >>> MonitoredFocusList()._get_focus()
+ >>> MonitoredFocusList().focus
"""
if not self:
return None
return self._focus
- def _set_focus(self, index: int) -> None:
+ @focus.setter
+ def focus(self, index: int) -> None:
"""
index -- index into this list, any index out of range will
raise an IndexError, except when the list is empty and
@@ -157,11 +164,11 @@ class MonitoredFocusList(MonitoredList):
instance with set_focus_changed_callback().
>>> ml = MonitoredFocusList([9, 10, 11])
- >>> ml._set_focus(2); ml._get_focus()
+ >>> ml.focus = 2; ml.focus
2
- >>> ml._set_focus(0); ml._get_focus()
+ >>> ml.focus = 0; ml.focus
0
- >>> ml._set_focus(-2)
+ >>> ml.focus = -2
Traceback (most recent call last):
...
IndexError: focus index is out of range: -2
@@ -178,11 +185,23 @@ class MonitoredFocusList(MonitoredList):
self._focus_changed(index)
self._focus = index
- focus = property(_get_focus, _set_focus, doc="""
- Get/set the focus index. This value is read as None when the list
- is empty, and may only be set to a value between 0 and len(self)-1
- or an IndexError will be raised.
- """)
+ def _get_focus(self) -> int | None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._get_focus` is deprecated, "
+ f"please use `{self.__class__.__name__}.focus` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.focus
+
+ def _set_focus(self, index: int) -> None:
+ warnings.warn(
+ f"method `{self.__class__.__name__}._set_focus` is deprecated, "
+ f"please use `{self.__class__.__name__}.focus` property",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.focus = index
def _focus_changed(self, new_focus: int):
pass
@@ -306,7 +325,7 @@ class MonitoredFocusList(MonitoredList):
else:
focus = self._adjust_focus_on_contents_modified(slice(y, y+1 or None))
rval = super().__delitem__(y)
- self._set_focus(focus)
+ self.focus = focus
return rval
def __setitem__(self, i: int | slice, y):
@@ -343,7 +362,7 @@ class MonitoredFocusList(MonitoredList):
else:
focus = self._adjust_focus_on_contents_modified(slice(i, i+1 or None), [y])
rval = super().__setitem__(i, y)
- self._set_focus(focus)
+ self.focus = focus
return rval
def __imul__(self, n: int):
@@ -366,7 +385,7 @@ class MonitoredFocusList(MonitoredList):
else: # all contents are being removed
focus = self._adjust_focus_on_contents_modified(slice(0, len(self)))
rval = super().__imul__(n)
- self._set_focus(focus)
+ self.focus = focus
return rval
def append(self, item):
@@ -381,7 +400,7 @@ class MonitoredFocusList(MonitoredList):
focus = self._adjust_focus_on_contents_modified(
slice(len(self), len(self)), [item])
rval = super().append(item)
- self._set_focus(focus)
+ self.focus = focus
return rval
def extend(self, items):
@@ -396,7 +415,7 @@ class MonitoredFocusList(MonitoredList):
focus = self._adjust_focus_on_contents_modified(
slice(len(self), len(self)), items)
rval = super().extend(items)
- self._set_focus(focus)
+ self.focus = focus
return rval
def insert(self, index: int, item):
@@ -411,7 +430,7 @@ class MonitoredFocusList(MonitoredList):
"""
focus = self._adjust_focus_on_contents_modified(slice(index, index), [item])
rval = super().insert(index, item)
- self._set_focus(focus)
+ self.focus = focus
return rval
def pop(self, index: int = -1):
@@ -432,7 +451,7 @@ class MonitoredFocusList(MonitoredList):
"""
focus = self._adjust_focus_on_contents_modified(slice(index, index + 1 or None))
rval = super().pop(index)
- self._set_focus(focus)
+ self.focus = focus
return rval
def remove(self, value):
@@ -449,7 +468,7 @@ class MonitoredFocusList(MonitoredList):
focus = self._adjust_focus_on_contents_modified(slice(index,
index+1 or None))
rval = super().remove(value)
- self._set_focus(focus)
+ self.focus = focus
return rval
def reverse(self):
@@ -459,7 +478,7 @@ class MonitoredFocusList(MonitoredList):
MonitoredFocusList([4, 3, 2, 1, 0], focus=3)
"""
rval = super().reverse()
- self._set_focus(max(0, len(self) - self._focus - 1))
+ self.focus = max(0, len(self) - self._focus - 1)
return rval
def sort(self, **kwargs):
@@ -472,7 +491,7 @@ class MonitoredFocusList(MonitoredList):
return
value = self[self._focus]
rval = super().sort(**kwargs)
- self._set_focus(self.index(value))
+ self.focus = self.index(value)
return rval
if hasattr(list, 'clear'):
diff --git a/urwid/tests/test_container.py b/urwid/tests/test_container.py
index f5a9182..3796cc7 100644
--- a/urwid/tests/test_container.py
+++ b/urwid/tests/test_container.py
@@ -134,6 +134,11 @@ class PileTest(unittest.TestCase):
p.mouse_event((5,), 'button press', 1, 1, 1, False)
p.mouse_event((5,), 'button press', 1, 1, 1, True)
+ def test_length(self):
+ pile = urwid.Pile(urwid.Text(c) for c in "ABC")
+ self.assertEqual(3, len(pile))
+ self.assertEqual(3, len(pile.contents))
+
class ColumnsTest(unittest.TestCase):
def cwtest(self, desc, l, divide, size, exp, focus_column=0):
@@ -202,8 +207,7 @@ class ColumnsTest(unittest.TestCase):
c = urwid.Columns( l, divide )
rval = c.move_cursor_to_coords( size, col, row )
assert rval == exp, f"{desc} expected {exp!r}, got {rval!r}"
- assert c.focus_col == f_col, "%s expected focus_col %s got %s"%(
- desc, f_col, c.focus_col)
+ assert c.focus_position == f_col, f"{desc} expected focus_col {f_col} got {c.focus_position}"
pc = c.get_pref_col( size )
assert pc == pref_col, f"{desc} expected pref_col {pref_col}, got {pc}"
@@ -252,6 +256,10 @@ class ColumnsTest(unittest.TestCase):
c.mouse_event((10,), 'foo', 1, 0, 0, True)
c.get_pref_col((10,))
+ def test_length(self):
+ columns = urwid.Columns(urwid.Text(c) for c in "ABC")
+ self.assertEqual(3, len(columns))
+ self.assertEqual(3, len(columns.contents))
class OverlayTest(unittest.TestCase):
@@ -274,6 +282,18 @@ class OverlayTest(unittest.TestCase):
urwid.SolidFill('B'),
'right', 1, 'bottom', 1).get_cursor_coords((2,2)), (1,1))
+ def test_length(self):
+ ovl = urwid.Overlay(
+ urwid.SolidFill('X'),
+ urwid.SolidFill('O'),
+ 'center',
+ ("relative", 20),
+ "middle",
+ ("relative", 20),
+ )
+ self.assertEqual(2, len(ovl))
+ self.assertEqual(2, len(ovl.contents))
+
class GridFlowTest(unittest.TestCase):
def test_cell_width(self):
@@ -298,6 +318,11 @@ class GridFlowTest(unittest.TestCase):
self.assertEqual(gf.keypress((20,), "enter"), None)
call_back.assert_called_with(button)
+ def test_length(self):
+ grid = urwid.GridFlow((urwid.Text(c) for c in "ABC"), 1, 0, 0, 'left')
+ self.assertEqual(3, len(grid))
+ self.assertEqual(3, len(grid.contents))
+
class WidgetSquishTest(unittest.TestCase):
def wstest(self, w):
diff --git a/urwid/widget.py b/urwid/widget.py
index d5abf40..71ade15 100644
--- a/urwid/widget.py
+++ b/urwid/widget.py
@@ -25,6 +25,7 @@ from __future__ import annotations
import functools
import typing
import warnings
+from collections.abc import Callable
from operator import attrgetter
from urwid import signals, text_layout
@@ -1338,17 +1339,6 @@ class Edit(Text):
else:
return pref_col
- def update_text(self) -> typing.NoReturn:
- """
- No longer supported.
-
- >>> Edit().update_text()
- Traceback (most recent call last):
- EditError: update_text() has been removed. Use set_caption() or set_edit_text() instead.
- """
- raise EditError("update_text() has been removed. Use "
- "set_caption() or set_edit_text() instead.")
-
def set_caption(self, caption):
"""
Set the caption markup for this widget.
@@ -1906,6 +1896,31 @@ class WidgetWrap(delegate_to_widget_mixin('_wrapped_widget'), Widget):
"""
self._wrapped_widget = w
+ @property
+ def _w(self) -> Widget:
+ return self._wrapped_widget
+
+ @_w.setter
+ def _w(self, new_widget: Widget) -> None:
+ """
+ Change the wrapped widget. This is meant to be called
+ only by subclasses.
+
+ >>> size = (10,)
+ >>> ww = WidgetWrap(Edit("hello? ","hi"))
+ >>> ww.render(size).text # ... = b in Python 3
+ [...'hello? hi ']
+ >>> ww.selectable()
+ True
+ >>> ww._w = Text("goodbye") # calls _set_w()
+ >>> ww.render(size).text
+ [...'goodbye ']
+ >>> ww.selectable()
+ False
+ """
+ self._wrapped_widget = new_widget
+ self._invalidate()
+
def _set_w(self, w):
"""
Change the wrapped widget. This is meant to be called
@@ -1923,17 +1938,13 @@ class WidgetWrap(delegate_to_widget_mixin('_wrapped_widget'), Widget):
>>> ww.selectable()
False
"""
+ warnings.warn(
+ "_set_w is deprecated. Please use 'WidgetWrap._w' property directly",
+ DeprecationWarning,
+ stacklevel=2,
+ )
self._wrapped_widget = w
self._invalidate()
- _w = property(lambda self:self._wrapped_widget, _set_w)
-
- def _raise_old_name_error(self, val=None):
- raise WidgetWrapError("The WidgetWrap.w member variable has "
- "been renamed to WidgetWrap._w (not intended for use "
- "outside the class and its subclasses). "
- "Please update your code to use self._w "
- "instead of self.w.")
- w = property(_raise_old_name_error, _raise_old_name_error)
def _test():