urwid 2.6.4__py3-none-any.whl → 2.6.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of urwid might be problematic. Click here for more details.

Files changed (46) hide show
  1. urwid/__init__.py +46 -33
  2. urwid/canvas.py +4 -6
  3. urwid/display/_posix_raw_display.py +3 -4
  4. urwid/display/_raw_display_base.py +4 -5
  5. urwid/display/_win32_raw_display.py +1 -2
  6. urwid/display/curses.py +1 -1
  7. urwid/display/html_fragment.py +1 -1
  8. urwid/event_loop/__init__.py +5 -5
  9. urwid/event_loop/main_loop.py +26 -21
  10. urwid/event_loop/zmq_loop.py +2 -2
  11. urwid/numedit.py +5 -1
  12. urwid/signals.py +1 -1
  13. urwid/str_util.py +1 -2
  14. urwid/text_layout.py +8 -4
  15. urwid/version.py +2 -2
  16. urwid/vterm.py +10 -10
  17. urwid/widget/__init__.py +19 -1
  18. urwid/widget/bar_graph.py +8 -4
  19. urwid/widget/big_text.py +14 -4
  20. urwid/widget/box_adapter.py +11 -3
  21. urwid/widget/columns.py +104 -34
  22. urwid/widget/constants.py +43 -76
  23. urwid/widget/container.py +2 -2
  24. urwid/widget/divider.py +5 -1
  25. urwid/widget/edit.py +20 -14
  26. urwid/widget/filler.py +11 -4
  27. urwid/widget/frame.py +91 -28
  28. urwid/widget/grid_flow.py +66 -15
  29. urwid/{listbox.py → widget/listbox.py} +27 -23
  30. urwid/{monitored_list.py → widget/monitored_list.py} +2 -0
  31. urwid/widget/overlay.py +99 -14
  32. urwid/widget/padding.py +16 -3
  33. urwid/widget/pile.py +57 -13
  34. urwid/widget/progress_bar.py +5 -1
  35. urwid/widget/scrollable.py +28 -9
  36. urwid/widget/solid_fill.py +5 -1
  37. urwid/widget/text.py +11 -3
  38. urwid/{treetools.py → widget/treetools.py} +35 -18
  39. urwid/widget/widget.py +17 -9
  40. urwid/widget/wimp.py +8 -4
  41. {urwid-2.6.4.dist-info → urwid-2.6.6.dist-info}/METADATA +1 -1
  42. urwid-2.6.6.dist-info/RECORD +74 -0
  43. urwid-2.6.4.dist-info/RECORD +0 -74
  44. {urwid-2.6.4.dist-info → urwid-2.6.6.dist-info}/COPYING +0 -0
  45. {urwid-2.6.4.dist-info → urwid-2.6.6.dist-info}/WHEEL +0 -0
  46. {urwid-2.6.4.dist-info → urwid-2.6.6.dist-info}/top_level.txt +0 -0
urwid/widget/frame.py CHANGED
@@ -4,6 +4,7 @@ import typing
4
4
  import warnings
5
5
 
6
6
  from urwid.canvas import CanvasCombine, CompositeCanvas
7
+ from urwid.split_repr import remove_defaults
7
8
  from urwid.util import is_mouse_press
8
9
 
9
10
  from .constants import Sizing, VAlign
@@ -17,6 +18,11 @@ if typing.TYPE_CHECKING:
17
18
  from typing_extensions import Literal
18
19
 
19
20
 
21
+ BodyWidget = typing.TypeVar("BodyWidget")
22
+ HeaderWidget = typing.TypeVar("HeaderWidget")
23
+ FooterWidget = typing.TypeVar("FooterWidget")
24
+
25
+
20
26
  class FrameError(WidgetError):
21
27
  pass
22
28
 
@@ -34,15 +40,14 @@ def _check_widget_subclass(widget: Widget | None) -> None:
34
40
  )
35
41
 
36
42
 
37
- class Frame(Widget, WidgetContainerMixin):
43
+ class Frame(Widget, WidgetContainerMixin, typing.Generic[BodyWidget, HeaderWidget, FooterWidget]):
38
44
  """
39
45
  Frame widget is a box widget with optional header and footer
40
46
  flow widgets placed above and below the box widget.
41
47
 
42
48
  .. note:: The main difference between a Frame and a :class:`Pile` widget
43
49
  defined as: `Pile([('pack', header), body, ('pack', footer)])` is that
44
- the Frame will not automatically change focus up and down in response to
45
- keystrokes.
50
+ the Frame will not automatically change focus up and down in response to keystrokes.
46
51
  """
47
52
 
48
53
  _selectable = True
@@ -50,9 +55,9 @@ class Frame(Widget, WidgetContainerMixin):
50
55
 
51
56
  def __init__(
52
57
  self,
53
- body: Widget,
54
- header: Widget | None = None,
55
- footer: Widget | None = None,
58
+ body: BodyWidget,
59
+ header: HeaderWidget = None,
60
+ footer: FooterWidget = None,
56
61
  focus_part: Literal["header", "footer", "body"] | Widget = "body",
57
62
  ):
58
63
  """
@@ -85,19 +90,35 @@ class Frame(Widget, WidgetContainerMixin):
85
90
  _check_widget_subclass(body)
86
91
  _check_widget_subclass(footer)
87
92
 
93
+ def _repr_attrs(self) -> dict[str, typing.Any]:
94
+ attrs = {
95
+ **super()._repr_attrs(),
96
+ "body": self._body,
97
+ "header": self._header,
98
+ "footer": self._footer,
99
+ "focus_part": self.focus_part,
100
+ }
101
+ return remove_defaults(attrs, Frame.__init__)
102
+
103
+ def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
104
+ yield "body", self._body
105
+ yield "header", self._header
106
+ yield "footer", self._footer
107
+ yield "focus_part", self.focus_part
108
+
88
109
  @property
89
- def header(self) -> Widget | None:
110
+ def header(self) -> HeaderWidget:
90
111
  return self._header
91
112
 
92
113
  @header.setter
93
- def header(self, header: Widget | None) -> None:
114
+ def header(self, header: HeaderWidget) -> None:
94
115
  _check_widget_subclass(header)
95
116
  self._header = header
96
117
  if header is None and self.focus_part == "header":
97
118
  self.focus_part = "body"
98
119
  self._invalidate()
99
120
 
100
- def get_header(self) -> Widget | None:
121
+ def get_header(self) -> HeaderWidget:
101
122
  warnings.warn(
102
123
  f"method `{self.__class__.__name__}.get_header` is deprecated, "
103
124
  f"standard property `{self.__class__.__name__}.header` should be used instead",
@@ -106,7 +127,7 @@ class Frame(Widget, WidgetContainerMixin):
106
127
  )
107
128
  return self.header
108
129
 
109
- def set_header(self, header: Widget | None):
130
+ def set_header(self, header: HeaderWidget) -> None:
110
131
  warnings.warn(
111
132
  f"method `{self.__class__.__name__}.set_header` is deprecated, "
112
133
  f"standard property `{self.__class__.__name__}.header` should be used instead",
@@ -116,16 +137,16 @@ class Frame(Widget, WidgetContainerMixin):
116
137
  self.header = header
117
138
 
118
139
  @property
119
- def body(self) -> Widget:
140
+ def body(self) -> BodyWidget:
120
141
  return self._body
121
142
 
122
143
  @body.setter
123
- def body(self, body: Widget) -> None:
144
+ def body(self, body: BodyWidget) -> None:
124
145
  _check_widget_subclass(body)
125
146
  self._body = body
126
147
  self._invalidate()
127
148
 
128
- def get_body(self) -> Widget:
149
+ def get_body(self) -> BodyWidget:
129
150
  warnings.warn(
130
151
  f"method `{self.__class__.__name__}.get_body` is deprecated, "
131
152
  f"standard property {self.__class__.__name__}.body should be used instead",
@@ -134,7 +155,7 @@ class Frame(Widget, WidgetContainerMixin):
134
155
  )
135
156
  return self.body
136
157
 
137
- def set_body(self, body: Widget) -> None:
158
+ def set_body(self, body: BodyWidget) -> None:
138
159
  warnings.warn(
139
160
  f"method `{self.__class__.__name__}.set_body` is deprecated, "
140
161
  f"standard property `{self.__class__.__name__}.body` should be used instead",
@@ -144,18 +165,18 @@ class Frame(Widget, WidgetContainerMixin):
144
165
  self.body = body
145
166
 
146
167
  @property
147
- def footer(self) -> Widget | None:
168
+ def footer(self) -> FooterWidget:
148
169
  return self._footer
149
170
 
150
171
  @footer.setter
151
- def footer(self, footer: Widget | None) -> None:
172
+ def footer(self, footer: FooterWidget) -> None:
152
173
  _check_widget_subclass(footer)
153
174
  self._footer = footer
154
175
  if footer is None and self.focus_part == "footer":
155
176
  self.focus_part = "body"
156
177
  self._invalidate()
157
178
 
158
- def get_footer(self) -> Widget | None:
179
+ def get_footer(self) -> FooterWidget:
159
180
  warnings.warn(
160
181
  f"method `{self.__class__.__name__}.get_footer` is deprecated, "
161
182
  f"standard property `{self.__class__.__name__}.footer` should be used instead",
@@ -164,7 +185,7 @@ class Frame(Widget, WidgetContainerMixin):
164
185
  )
165
186
  return self.footer
166
187
 
167
- def set_footer(self, footer: Widget | None) -> None:
188
+ def set_footer(self, footer: FooterWidget) -> None:
168
189
  warnings.warn(
169
190
  f"method `{self.__class__.__name__}.set_footer` is deprecated, "
170
191
  f"standard property `{self.__class__.__name__}.footer` should be used instead",
@@ -228,13 +249,13 @@ class Frame(Widget, WidgetContainerMixin):
228
249
  self.focus_position = part
229
250
 
230
251
  @property
231
- def focus(self) -> Widget:
252
+ def focus(self) -> BodyWidget | HeaderWidget | FooterWidget:
232
253
  """
233
254
  child :class:`Widget` in focus: the body, header or footer widget.
234
255
  This is a read-only property."""
235
256
  return {"header": self._header, "footer": self._footer, "body": self._body}[self.focus_part]
236
257
 
237
- def _get_focus(self) -> Widget:
258
+ def _get_focus(self) -> BodyWidget | HeaderWidget | FooterWidget:
238
259
  warnings.warn(
239
260
  f"method `{self.__class__.__name__}._get_focus` is deprecated, "
240
261
  f"please use `{self.__class__.__name__}.focus` property",
@@ -244,7 +265,12 @@ class Frame(Widget, WidgetContainerMixin):
244
265
  return {"header": self._header, "footer": self._footer, "body": self._body}[self.focus_part]
245
266
 
246
267
  @property
247
- def contents(self) -> MutableMapping[Literal["header", "footer", "body"], tuple[Widget, None]]:
268
+ def contents(
269
+ self,
270
+ ) -> MutableMapping[
271
+ Literal["header", "footer", "body"],
272
+ tuple[BodyWidget | HeaderWidget | FooterWidget, None],
273
+ ]:
248
274
  """
249
275
  a dict-like object similar to::
250
276
 
@@ -267,7 +293,12 @@ class Frame(Widget, WidgetContainerMixin):
267
293
  """
268
294
 
269
295
  # noinspection PyMethodParameters
270
- class FrameContents(typing.MutableMapping[str, typing.Tuple[Widget, None]]):
296
+ class FrameContents(
297
+ typing.MutableMapping[
298
+ str,
299
+ typing.Tuple[typing.Union[BodyWidget, HeaderWidget, FooterWidget], None],
300
+ ]
301
+ ):
271
302
  # pylint: disable=no-self-argument
272
303
 
273
304
  __slots__ = ()
@@ -298,7 +329,18 @@ class Frame(Widget, WidgetContainerMixin):
298
329
  keys.append("footer")
299
330
  return keys
300
331
 
301
- def _contents__getitem__(self, key: Literal["header", "footer", "body"]):
332
+ @typing.overload
333
+ def _contents__getitem__(self, key: Literal["body"]) -> tuple[BodyWidget, None]: ...
334
+
335
+ @typing.overload
336
+ def _contents__getitem__(self, key: Literal["header"]) -> tuple[HeaderWidget, None]: ...
337
+
338
+ @typing.overload
339
+ def _contents__getitem__(self, key: Literal["footer"]) -> tuple[FooterWidget, None]: ...
340
+
341
+ def _contents__getitem__(
342
+ self, key: Literal["body", "header", "footer"]
343
+ ) -> tuple[BodyWidget | HeaderWidget | FooterWidget, None]:
302
344
  if key == "body":
303
345
  return (self._body, None)
304
346
  if key == "header" and self._header:
@@ -307,7 +349,20 @@ class Frame(Widget, WidgetContainerMixin):
307
349
  return (self._footer, None)
308
350
  raise KeyError(f"Frame.contents has no key: {key!r}")
309
351
 
310
- def _contents__setitem__(self, key: Literal["header", "footer", "body"], value) -> None:
352
+ @typing.overload
353
+ def _contents__setitem__(self, key: Literal["body"], value: tuple[BodyWidget, None]) -> None: ...
354
+
355
+ @typing.overload
356
+ def _contents__setitem__(self, key: Literal["header"], value: tuple[HeaderWidget, None]) -> None: ...
357
+
358
+ @typing.overload
359
+ def _contents__setitem__(self, key: Literal["footer"], value: tuple[FooterWidget, None]) -> None: ...
360
+
361
+ def _contents__setitem__(
362
+ self,
363
+ key: Literal["body", "header", "footer"],
364
+ value: tuple[BodyWidget | HeaderWidget | FooterWidget, None],
365
+ ) -> None:
311
366
  if key not in {"body", "header", "footer"}:
312
367
  raise KeyError(f"Frame.contents has no key: {key!r}")
313
368
  try:
@@ -323,7 +378,7 @@ class Frame(Widget, WidgetContainerMixin):
323
378
  else:
324
379
  self.header = value_w
325
380
 
326
- def _contents__delitem__(self, key: Literal["header", "footer", "body"]) -> None:
381
+ def _contents__delitem__(self, key: Literal["header", "footer"]) -> None:
327
382
  if key not in {"header", "footer"}:
328
383
  raise KeyError(f"Frame.contents can't remove key: {key!r}")
329
384
  if (key == "header" and self._header is None) or (key == "footer" and self._footer is None):
@@ -400,7 +455,11 @@ class Frame(Widget, WidgetContainerMixin):
400
455
 
401
456
  return (hrows, frows), (hrows, frows)
402
457
 
403
- def render(self, size: tuple[int, int], focus: bool = False) -> CompositeCanvas:
458
+ def render(
459
+ self,
460
+ size: tuple[int, int], # type: ignore[override]
461
+ focus: bool = False,
462
+ ) -> CompositeCanvas:
404
463
  (maxcol, maxrow) = size
405
464
  (htrim, ftrim), (hrows, frows) = self.frame_top_bottom((maxcol, maxrow), focus)
406
465
 
@@ -436,7 +495,11 @@ class Frame(Widget, WidgetContainerMixin):
436
495
 
437
496
  return CanvasCombine(combinelist)
438
497
 
439
- def keypress(self, size: tuple[int, int], key: str) -> str | None:
498
+ def keypress(
499
+ self,
500
+ size: tuple[int, int], # type: ignore[override]
501
+ key: str,
502
+ ) -> str | None:
440
503
  """Pass keypress to widget in focus."""
441
504
  (maxcol, maxrow) = size
442
505
 
@@ -464,7 +527,7 @@ class Frame(Widget, WidgetContainerMixin):
464
527
 
465
528
  def mouse_event(
466
529
  self,
467
- size: tuple[int, int],
530
+ size: tuple[int, int], # type: ignore[override]
468
531
  event: str,
469
532
  button: int,
470
533
  col: int,
urwid/widget/grid_flow.py CHANGED
@@ -3,24 +3,29 @@ from __future__ import annotations
3
3
  import typing
4
4
  import warnings
5
5
 
6
- from urwid.monitored_list import MonitoredFocusList, MonitoredList
6
+ from urwid.split_repr import remove_defaults
7
7
 
8
8
  from .columns import Columns
9
9
  from .constants import Align, Sizing, WHSettings
10
10
  from .container import WidgetContainerListContentsMixin, WidgetContainerMixin
11
11
  from .divider import Divider
12
+ from .monitored_list import MonitoredFocusList, MonitoredList
12
13
  from .padding import Padding
13
14
  from .pile import Pile
14
- from .widget import Widget, WidgetError, WidgetWrap
15
+ from .widget import Widget, WidgetError, WidgetWarning, WidgetWrap
15
16
 
16
17
  if typing.TYPE_CHECKING:
17
- from collections.abc import Iterable, Sequence
18
+ from collections.abc import Iterable, Iterator, Sequence
18
19
 
19
20
  from typing_extensions import Literal
20
21
 
21
22
 
22
23
  class GridFlowError(WidgetError):
23
- pass
24
+ """GridFlow specific error."""
25
+
26
+
27
+ class GridFlowWarning(WidgetWarning):
28
+ """GridFlow specific warning."""
24
29
 
25
30
 
26
31
  class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListContentsMixin):
@@ -75,6 +80,37 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
75
80
  self._cache_maxcol = self._get_maxcol(())
76
81
  super().__init__(self.generate_display_widget((self._cache_maxcol,)))
77
82
 
83
+ def _repr_words(self) -> list[str]:
84
+ if len(self.contents) > 1:
85
+ contents_string = f"({len(self.contents)} items)"
86
+ elif self.contents:
87
+ contents_string = "(1 item)"
88
+ else:
89
+ contents_string = "()"
90
+ return [*super()._repr_words(), contents_string]
91
+
92
+ def _repr_attrs(self) -> dict[str, typing.Any]:
93
+ attrs = {
94
+ **super()._repr_attrs(),
95
+ "cell_width": self.cell_width,
96
+ "h_sep": self.h_sep,
97
+ "v_sep": self.v_sep,
98
+ "align": self.align,
99
+ "focus": self.focus_position if len(self._contents) > 1 else None,
100
+ }
101
+ return remove_defaults(attrs, GridFlow.__init__)
102
+
103
+ def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
104
+ yield "cells", [widget for widget, _ in self.contents]
105
+ yield "cell_width", self.cell_width
106
+ yield "h_sep", self.h_sep
107
+ yield "v_sep", self.v_sep
108
+ yield "align", self.align
109
+ yield "focus", self.focus_position
110
+
111
+ def __len__(self) -> int:
112
+ return len(self._contents)
113
+
78
114
  def _invalidate(self) -> None:
79
115
  self._cache_maxcol = None
80
116
  super()._invalidate()
@@ -123,7 +159,7 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
123
159
  )
124
160
  focus_position = self.focus_position
125
161
  self.contents = [(new, (WHSettings.GIVEN, self._cell_width)) for new in widgets]
126
- if focus_position < len(widgets):
162
+ if focus_position < len(widgets): # pylint: disable=consider-using-max-builtin # pylint bug
127
163
  self.focus_position = focus_position
128
164
 
129
165
  def _get_cells(self):
@@ -384,6 +420,12 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
384
420
  def _get_maxcol(self, size: tuple[int] | tuple[()]) -> int:
385
421
  if size:
386
422
  (maxcol,) = size
423
+ if maxcol < self.cell_width:
424
+ warnings.warn(
425
+ f"Size is smaller than cell width ({maxcol!r} < {self.cell_width!r})",
426
+ GridFlowWarning,
427
+ stacklevel=3,
428
+ )
387
429
  else:
388
430
  maxcol = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
389
431
  return maxcol
@@ -435,18 +477,15 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
435
477
  pad.first_position = i
436
478
  p.contents.append((pad, p.options()))
437
479
 
438
- c.contents.append((w, c.options(WHSettings.GIVEN, width_amount)))
480
+ # Use width == maxcol in case of maxcol < width amount
481
+ # Columns will use empty widget in case of GIVEN width > maxcol
482
+ c.contents.append((w, c.options(WHSettings.GIVEN, width_amount if width_amount <= maxcol else maxcol)))
439
483
  if (i == self.focus_position) or (not column_focused and w.selectable()):
440
484
  c.focus_position = len(c.contents) - 1
441
485
  column_focused = True
442
486
  if i == self.focus_position:
443
487
  p.focus_position = len(p.contents) - 1
444
488
  used_space = sum(x[1][1] for x in c.contents) + self.h_sep * len(c.contents)
445
- if width_amount > maxcol:
446
- # special case: display is too narrow for the given
447
- # width so we remove the Columns for better behaviour
448
- # FIXME: determine why this is necessary
449
- pad.original_widget = w
450
489
  pad.width = used_space - self.h_sep
451
490
 
452
491
  if self.v_sep:
@@ -482,7 +521,11 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
482
521
  # pad.first_position was set by generate_display_widget() above
483
522
  self.focus_position = pile_focus.first_position + col_focus_position
484
523
 
485
- def keypress(self, size: tuple[int], key: str) -> str | None:
524
+ def keypress(
525
+ self,
526
+ size: tuple[int], # type: ignore[override]
527
+ key: str,
528
+ ) -> str | None:
486
529
  """
487
530
  Pass keypress to display widget for handling.
488
531
  Captures focus changes.
@@ -493,7 +536,11 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
493
536
  self._set_focus_from_display_widget()
494
537
  return key
495
538
 
496
- def pack(self, size: tuple[int] | tuple[()] = (), focus: bool = False) -> tuple[int, int]:
539
+ def pack(
540
+ self,
541
+ size: tuple[int] | tuple[()] = (), # type: ignore[override]
542
+ focus: bool = False,
543
+ ) -> tuple[int, int]:
497
544
  if size:
498
545
  return super().pack(size, focus)
499
546
  cols = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
@@ -503,7 +550,11 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
503
550
  self.get_display_widget(size)
504
551
  return super().rows(size, focus=focus)
505
552
 
506
- def render(self, size: tuple[int] | tuple[()], focus: bool = False):
553
+ def render(
554
+ self,
555
+ size: tuple[int] | tuple[()], # type: ignore[override]
556
+ focus: bool = False,
557
+ ):
507
558
  self.get_display_widget(size)
508
559
  return super().render(size, focus)
509
560
 
@@ -521,7 +572,7 @@ class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListConten
521
572
 
522
573
  def mouse_event(
523
574
  self,
524
- size: tuple[int] | tuple[()],
575
+ size: tuple[int] | tuple[()], # type: ignore[override]
525
576
  event: str,
526
577
  button: int,
527
578
  col: int,
@@ -29,20 +29,12 @@ from typing_extensions import Protocol, runtime_checkable
29
29
 
30
30
  from urwid import signals
31
31
  from urwid.canvas import CanvasCombine, SolidCanvas
32
- from urwid.command_map import Command
33
- from urwid.monitored_list import MonitoredFocusList, MonitoredList
34
- from urwid.signals import connect_signal, disconnect_signal
35
- from urwid.util import is_mouse_press
36
- from urwid.widget import (
37
- Sizing,
38
- VAlign,
39
- WHSettings,
40
- Widget,
41
- WidgetContainerMixin,
42
- calculate_top_bottom_filler,
43
- nocache_widget_render_instance,
44
- normalize_valign,
45
- )
32
+
33
+ from .constants import Sizing, VAlign, WHSettings, normalize_valign
34
+ from .container import WidgetContainerMixin
35
+ from .filler import calculate_top_bottom_filler
36
+ from .monitored_list import MonitoredFocusList, MonitoredList
37
+ from .widget import Widget, nocache_widget_render_instance
46
38
 
47
39
  if typing.TYPE_CHECKING:
48
40
  from collections.abc import Callable, Hashable
@@ -51,6 +43,8 @@ if typing.TYPE_CHECKING:
51
43
 
52
44
  from urwid.canvas import Canvas, CompositeCanvas
53
45
 
46
+ __all__ = ("ListWalkerError", "ListWalker", "SimpleListWalker", "SimpleFocusListWalker", "ListBoxError", "ListBox")
47
+
54
48
  _T = typing.TypeVar("_T")
55
49
  _K = typing.TypeVar("_K")
56
50
 
@@ -362,7 +356,7 @@ class ListBox(Widget, WidgetContainerMixin):
362
356
  @body.setter
363
357
  def body(self, body: Iterable[Widget] | ListWalker) -> None:
364
358
  with suppress(AttributeError):
365
- disconnect_signal(self._body, "modified", self._invalidate)
359
+ signals.disconnect_signal(self._body, "modified", self._invalidate)
366
360
  # _body may be not yet assigned
367
361
 
368
362
  if getattr(body, "get_focus", None):
@@ -370,7 +364,7 @@ class ListBox(Widget, WidgetContainerMixin):
370
364
  else:
371
365
  self._body = SimpleListWalker(body)
372
366
  try:
373
- connect_signal(self._body, "modified", self._invalidate)
367
+ signals.connect_signal(self._body, "modified", self._invalidate)
374
368
  except NameError:
375
369
  # our list walker has no modified signal so we must not
376
370
  # cache our canvases because we don't know when our
@@ -582,7 +576,11 @@ class ListBox(Widget, WidgetContainerMixin):
582
576
 
583
577
  return self._rows_max_cached
584
578
 
585
- def render(self, size: tuple[int, int], focus: bool = False) -> CompositeCanvas | SolidCanvas:
579
+ def render(
580
+ self,
581
+ size: tuple[int, int], # type: ignore[override]
582
+ focus: bool = False,
583
+ ) -> CompositeCanvas | SolidCanvas:
586
584
  """
587
585
  Render ListBox and return canvas.
588
586
 
@@ -1180,7 +1178,11 @@ class ListBox(Widget, WidgetContainerMixin):
1180
1178
  self.shift_focus((maxcol, maxrow), maxrow - cy - 1)
1181
1179
  return
1182
1180
 
1183
- def keypress(self, size: tuple[int, int], key: str) -> str | None:
1181
+ def keypress(
1182
+ self,
1183
+ size: tuple[int, int], # type: ignore[override]
1184
+ key: str,
1185
+ ) -> str | None:
1184
1186
  """Move selection through the list elements scrolling when
1185
1187
  necessary. Keystrokes are first passed to widget in focus
1186
1188
  in case that widget can handle them.
@@ -1191,6 +1193,8 @@ class ListBox(Widget, WidgetContainerMixin):
1191
1193
  'page up' move cursor up one listbox length (or widget)
1192
1194
  'page down' move cursor down one listbox length (or widget)
1193
1195
  """
1196
+ from urwid.command_map import Command
1197
+
1194
1198
  (maxcol, maxrow) = size
1195
1199
 
1196
1200
  if self.set_focus_pending or self.set_focus_valign_pending:
@@ -1773,7 +1777,7 @@ class ListBox(Widget, WidgetContainerMixin):
1773
1777
 
1774
1778
  def mouse_event(
1775
1779
  self,
1776
- size: tuple[int, int],
1780
+ size: tuple[int, int], # type: ignore[override]
1777
1781
  event,
1778
1782
  button: int,
1779
1783
  col: int,
@@ -1784,6 +1788,8 @@ class ListBox(Widget, WidgetContainerMixin):
1784
1788
  Pass the event to the contained widgets.
1785
1789
  May change focus on button 1 press.
1786
1790
  """
1791
+ from urwid.util import is_mouse_press
1792
+
1787
1793
  (maxcol, maxrow) = size
1788
1794
  middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=True)
1789
1795
  if middle is None:
@@ -1875,8 +1881,7 @@ class ListBox(Widget, WidgetContainerMixin):
1875
1881
  """
1876
1882
  positions_fn = getattr(self._body, "positions", None)
1877
1883
  if positions_fn:
1878
- for pos in positions_fn():
1879
- yield pos
1884
+ yield from positions_fn()
1880
1885
  return
1881
1886
 
1882
1887
  focus_widget, focus_pos = self._body.get_focus()
@@ -1907,8 +1912,7 @@ class ListBox(Widget, WidgetContainerMixin):
1907
1912
  """
1908
1913
  positions_fn = getattr(self._body, "positions", None)
1909
1914
  if positions_fn:
1910
- for pos in positions_fn(reverse=True):
1911
- yield pos
1915
+ yield from positions_fn(reverse=True)
1912
1916
  return
1913
1917
 
1914
1918
  focus_widget, focus_pos = self._body.get_focus()
@@ -31,6 +31,8 @@ if typing.TYPE_CHECKING:
31
31
  ArgSpec = ParamSpec("ArgSpec")
32
32
  Ret = typing.TypeVar("Ret")
33
33
 
34
+ __all__ = ("MonitoredList", "MonitoredFocusList")
35
+
34
36
  _T = typing.TypeVar("_T")
35
37
 
36
38