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/overlay.py CHANGED
@@ -4,6 +4,7 @@ import typing
4
4
  import warnings
5
5
 
6
6
  from urwid.canvas import CanvasOverlay, CompositeCanvas
7
+ from urwid.split_repr import remove_defaults
7
8
 
8
9
  from .constants import (
9
10
  RELATIVE_100,
@@ -32,6 +33,10 @@ if typing.TYPE_CHECKING:
32
33
  from typing_extensions import Literal
33
34
 
34
35
 
36
+ TopWidget = typing.TypeVar("TopWidget")
37
+ BottomWidget = typing.TypeVar("BottomWidget")
38
+
39
+
35
40
  class OverlayError(WidgetError):
36
41
  """Overlay specific errors."""
37
42
 
@@ -67,7 +72,7 @@ class OverlayOptions(typing.NamedTuple):
67
72
  bottom: int
68
73
 
69
74
 
70
- class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
75
+ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin, typing.Generic[TopWidget, BottomWidget]):
71
76
  """
72
77
  Overlay contains two box widgets and renders one on top of the other
73
78
  """
@@ -93,8 +98,8 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
93
98
 
94
99
  def __init__(
95
100
  self,
96
- top_w: Widget,
97
- bottom_w: Widget,
101
+ top_w: TopWidget,
102
+ bottom_w: BottomWidget,
98
103
  align: (
99
104
  Literal["left", "center", "right"]
100
105
  | Align
@@ -227,7 +232,11 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
227
232
 
228
233
  return frozenset(sizing)
229
234
 
230
- def pack(self, size: tuple[()] | tuple[int] | tuple[int, int] = (), focus: bool = False) -> tuple[int, int]:
235
+ def pack(
236
+ self,
237
+ size: tuple[()] | tuple[int] | tuple[int, int] = (),
238
+ focus: bool = False,
239
+ ) -> tuple[int, int]:
231
240
  if size:
232
241
  return super().pack(size, focus)
233
242
 
@@ -322,8 +331,67 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
322
331
  f"min_height={self.min_height!r}"
323
332
  )
324
333
 
325
- def _repr_words(self) -> list[str]:
326
- return [*super()._repr_words(), f"top={self.top_w!r}", f"bottom={self.bottom_w!r}"]
334
+ def _repr_attrs(self) -> dict[str, typing.Any]:
335
+ attrs = {
336
+ **super()._repr_attrs(),
337
+ "top_w": self.top_w,
338
+ "bottom_w": self.bottom_w,
339
+ "align": self.align,
340
+ "width": self.width,
341
+ "valign": self.valign,
342
+ "height": self.height,
343
+ "min_width": self.min_width,
344
+ "min_height": self.min_height,
345
+ "left": self.left,
346
+ "right": self.right,
347
+ "top": self.top,
348
+ "bottom": self.bottom,
349
+ }
350
+ return remove_defaults(attrs, Overlay.__init__)
351
+
352
+ def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
353
+ yield "top", self.top_w
354
+ yield "bottom", self.bottom_w
355
+ yield "align", self.align
356
+ yield "width", self.width
357
+ yield "valign", self.valign
358
+ yield "height", self.height
359
+ yield "min_width", self.min_width
360
+ yield "min_height", self.min_height
361
+ yield "left", self.left
362
+ yield "right", self.right
363
+ yield "top", self.top
364
+ yield "bottom", self.bottom
365
+
366
+ @property
367
+ def align(self) -> Align | tuple[Literal[WHSettings.RELATIVE], int]:
368
+ return simplify_align(self.align_type, self.align_amount)
369
+
370
+ @property
371
+ def width(
372
+ self,
373
+ ) -> (
374
+ Literal[WHSettings.CLIP, WHSettings.PACK]
375
+ | int
376
+ | tuple[Literal[WHSettings.RELATIVE], int]
377
+ | tuple[Literal[WHSettings.WEIGHT], int | float]
378
+ ):
379
+ return simplify_width(self.width_type, self.width_amount)
380
+
381
+ @property
382
+ def valign(self) -> VAlign | tuple[Literal[WHSettings.RELATIVE], int]:
383
+ return simplify_valign(self.valign_type, self.valign_amount)
384
+
385
+ @property
386
+ def height(
387
+ self,
388
+ ) -> (
389
+ int
390
+ | Literal[WHSettings.FLOW, WHSettings.PACK]
391
+ | tuple[Literal[WHSettings.RELATIVE], int]
392
+ | tuple[Literal[WHSettings.WEIGHT], int | float]
393
+ ):
394
+ return simplify_height(self.height_type, self.height_amount)
327
395
 
328
396
  @staticmethod
329
397
  def options(
@@ -478,13 +546,20 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
478
546
  """Return selectable from top_w."""
479
547
  return self.top_w.selectable()
480
548
 
481
- def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
549
+ def keypress(
550
+ self,
551
+ size: tuple[()] | tuple[int] | tuple[int, int],
552
+ key: str,
553
+ ) -> str | None:
482
554
  """Pass keypress to top_w."""
483
555
  real_size = self.pack(size, True)
484
- return self.top_w.keypress(self.top_w_size(real_size, *self.calculate_padding_filler(real_size, True)), key)
556
+ return self.top_w.keypress(
557
+ self.top_w_size(real_size, *self.calculate_padding_filler(real_size, True)),
558
+ key,
559
+ )
485
560
 
486
561
  @property
487
- def focus(self) -> Widget:
562
+ def focus(self) -> TopWidget:
488
563
  """
489
564
  Read-only property returning the child widget in focus for
490
565
  container widgets. This default implementation
@@ -492,7 +567,7 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
492
567
  """
493
568
  return self.top_w
494
569
 
495
- def _get_focus(self) -> Widget:
570
+ def _get_focus(self) -> TopWidget:
496
571
  warnings.warn(
497
572
  f"method `{self.__class__.__name__}._get_focus` is deprecated, "
498
573
  f"please use `{self.__class__.__name__}.focus` property",
@@ -567,7 +642,7 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
567
642
  """
568
643
 
569
644
  # noinspection PyMethodParameters
570
- class OverlayContents(typing.Sequence[typing.Tuple[Widget, OverlayOptions]]):
645
+ class OverlayContents(typing.Sequence[typing.Tuple[typing.Union[TopWidget, BottomWidget], OverlayOptions]]):
571
646
  # pylint: disable=no-self-argument
572
647
  def __len__(inner_self) -> int:
573
648
  return 2
@@ -595,7 +670,10 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
595
670
  self.contents[0] = new_contents[0]
596
671
  self.contents[1] = new_contents[1]
597
672
 
598
- def _contents__getitem__(self, index: Literal[0, 1]) -> tuple[Widget, OverlayOptions]:
673
+ def _contents__getitem__(
674
+ self,
675
+ index: Literal[0, 1],
676
+ ) -> tuple[TopWidget | BottomWidget, OverlayOptions]:
599
677
  if index == 0:
600
678
  return (self.bottom_w, self._DEFAULT_BOTTOM_OPTIONS)
601
679
 
@@ -621,7 +699,11 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
621
699
  )
622
700
  raise IndexError(f"Overlay.contents has no position {index!r}")
623
701
 
624
- def _contents__setitem__(self, index: Literal[0, 1], value: tuple[Widget, OverlayOptions]) -> None:
702
+ def _contents__setitem__(
703
+ self,
704
+ index: Literal[0, 1],
705
+ value: tuple[TopWidget | BottomWidget, OverlayOptions],
706
+ ) -> None:
625
707
  try:
626
708
  value_w, value_options = value
627
709
  except (ValueError, TypeError) as exc:
@@ -675,7 +757,10 @@ class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
675
757
  raise IndexError(f"Overlay.contents has no position {index!r}")
676
758
  self._invalidate()
677
759
 
678
- def get_cursor_coords(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> tuple[int, int] | None:
760
+ def get_cursor_coords(
761
+ self,
762
+ size: tuple[()] | tuple[int] | tuple[int, int],
763
+ ) -> tuple[int, int] | None:
679
764
  """Return cursor coords from top_w, if any."""
680
765
  if not hasattr(self.top_w, "get_cursor_coords"):
681
766
  return None
urwid/widget/padding.py CHANGED
@@ -20,8 +20,9 @@ from .constants import (
20
20
  from .widget_decoration import WidgetDecoration, WidgetError, WidgetWarning
21
21
 
22
22
  if typing.TYPE_CHECKING:
23
- from typing_extensions import Literal
23
+ from collections.abc import Iterator
24
24
 
25
+ from typing_extensions import Literal
25
26
 
26
27
  WrappedWidget = typing.TypeVar("WrappedWidget")
27
28
 
@@ -34,7 +35,7 @@ class PaddingWarning(WidgetWarning):
34
35
  """Padding related warnings."""
35
36
 
36
37
 
37
- class Padding(WidgetDecoration[WrappedWidget]):
38
+ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
38
39
  def __init__(
39
40
  self,
40
41
  w: WrappedWidget,
@@ -185,6 +186,14 @@ class Padding(WidgetDecoration[WrappedWidget]):
185
186
  }
186
187
  return remove_defaults(attrs, Padding.__init__)
187
188
 
189
+ def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
190
+ yield "w", self.original_widget
191
+ yield "align", self.align
192
+ yield "width", self.width
193
+ yield "min_width", self.min_width
194
+ yield "left", self.left
195
+ yield "right", self.right
196
+
188
197
  @property
189
198
  def align(
190
199
  self,
@@ -268,7 +277,11 @@ class Padding(WidgetDecoration[WrappedWidget]):
268
277
  )
269
278
  self.width = width
270
279
 
271
- def pack(self, size: tuple[()] | tuple[int] | tuple[int, int] = (), focus: bool = False) -> tuple[int, int]:
280
+ def pack(
281
+ self,
282
+ size: tuple[()] | tuple[int] | tuple[int, int] = (),
283
+ focus: bool = False,
284
+ ) -> tuple[int, int]:
272
285
  if size:
273
286
  return super().pack(size, focus)
274
287
  if self._width_type == WHSettings.CLIP:
urwid/widget/pile.py CHANGED
@@ -6,15 +6,16 @@ from itertools import chain, repeat
6
6
 
7
7
  from urwid.canvas import CanvasCombine, CompositeCanvas, SolidCanvas
8
8
  from urwid.command_map import Command
9
- from urwid.monitored_list import MonitoredFocusList, MonitoredList
9
+ from urwid.split_repr import remove_defaults
10
10
  from urwid.util import is_mouse_press
11
11
 
12
12
  from .constants import Sizing, WHSettings
13
13
  from .container import WidgetContainerListContentsMixin, WidgetContainerMixin, _ContainerElementSizingFlag
14
+ from .monitored_list import MonitoredFocusList, MonitoredList
14
15
  from .widget import Widget, WidgetError, WidgetWarning
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
 
@@ -58,36 +59,42 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
58
59
 
59
60
  # BOX-only widget
60
61
  >>> Pile((SolidFill("#"),))
61
- <Pile box widget>
62
+ <Pile box widget (1 item)>
62
63
 
63
64
  # GIVEN BOX -> BOX/FLOW
64
65
  >>> Pile(((10, SolidFill("#")),))
65
- <Pile box/flow widget>
66
+ <Pile box/flow widget (1 item)>
66
67
 
67
68
  # FLOW-only
68
69
  >>> Pile((ProgressBar(None, None),))
69
- <Pile flow widget>
70
+ <Pile flow widget (1 item)>
70
71
 
71
72
  # FIXED -> FIXED
72
73
  >>> Pile(((WHSettings.PACK, BigText("0", font)),))
73
- <Pile fixed widget>
74
+ <Pile fixed widget (1 item)>
74
75
 
75
76
  # FLOW/FIXED -> FLOW/FIXED
76
77
  >>> Pile(((WHSettings.PACK, Text("text")),))
77
- <Pile fixed/flow widget>
78
+ <Pile fixed/flow widget (1 item)>
78
79
 
79
80
  # FLOW + FIXED widgets -> FLOW/FIXED
80
81
  >>> Pile((ProgressBar(None, None), (WHSettings.PACK, BigText("0", font))))
81
- <Pile fixed/flow widget>
82
+ <Pile fixed/flow widget (2 items) focus_item=0>
82
83
 
83
84
  # GIVEN BOX + FIXED widgets -> BOX/FLOW/FIXED (GIVEN BOX allows overriding its height & allows any width)
84
85
  >>> Pile(((10, SolidFill("#")), (WHSettings.PACK, BigText("0", font))))
85
- <Pile widget>
86
+ <Pile widget (2 items) focus_item=0>
86
87
 
87
88
  # Invalid sizing combination -> use fallback settings (and produce warning)
88
89
  >>> Pile(((WHSettings.WEIGHT, 1, BigText("0", font)),))
89
- <Pile box/flow widget>
90
+ <Pile box/flow widget (1 item)>
91
+
92
+ # Special case: empty pile widget sizing is impossible to calculate
93
+ >>> Pile(())
94
+ <Pile box/flow widget ()>
90
95
  """
96
+ if not self.contents:
97
+ return frozenset((Sizing.BOX, Sizing.FLOW))
91
98
  strict_box = False
92
99
  has_flow = False
93
100
 
@@ -225,6 +232,43 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
225
232
 
226
233
  self.pref_col = 0
227
234
 
235
+ def _repr_words(self) -> list[str]:
236
+ if len(self.contents) > 1:
237
+ contents_string = f"({len(self.contents)} items)"
238
+ elif self.contents:
239
+ contents_string = "(1 item)"
240
+ else:
241
+ contents_string = "()"
242
+ return [*super()._repr_words(), contents_string]
243
+
244
+ def _repr_attrs(self) -> dict[str, typing.Any]:
245
+ attrs = {**super()._repr_attrs(), "focus_item": self.focus_position if len(self._contents) > 1 else None}
246
+ return remove_defaults(attrs, Pile.__init__)
247
+
248
+ def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
249
+ widget_list: list[
250
+ Widget
251
+ | tuple[Literal[WHSettings.PACK] | int, Widget]
252
+ | tuple[Literal[WHSettings.WEIGHT], int | float, Widget]
253
+ ] = []
254
+
255
+ for w_instance, (sizing, amount) in self._contents:
256
+ if sizing == WHSettings.GIVEN:
257
+ widget_list.append((amount, w_instance))
258
+ elif sizing == WHSettings.PACK:
259
+ widget_list.append((WHSettings.PACK, w_instance))
260
+ elif sizing == WHSettings.WEIGHT:
261
+ if amount == 1:
262
+ widget_list.append(w_instance)
263
+ else:
264
+ widget_list.append((WHSettings.WEIGHT, amount, w_instance))
265
+
266
+ yield "widget_list", widget_list
267
+ yield "focus_item", self.focus_position if self._contents else None
268
+
269
+ def __len__(self) -> int:
270
+ return len(self._contents)
271
+
228
272
  def _contents_modified(self) -> None:
229
273
  """Recalculate whether this widget should be selectable whenever the contents has been changed."""
230
274
  self._selectable = any(w.selectable() for w, o in self.contents)
@@ -281,7 +325,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
281
325
  chain(self.contents, repeat((None, (WHSettings.WEIGHT, 1)))),
282
326
  )
283
327
  ]
284
- if focus_position < len(widgets):
328
+ if focus_position < len(widgets): # pylint: disable=consider-using-max-builtin # pylint bug
285
329
  self.focus_position = focus_position
286
330
 
287
331
  @property
@@ -321,7 +365,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
321
365
  (w, ({Sizing.FIXED: WHSettings.GIVEN, Sizing.FLOW: WHSettings.PACK}.get(new_t, new_t), new_height))
322
366
  for ((new_t, new_height), (w, options)) in zip(item_types, self.contents)
323
367
  ]
324
- if focus_position < len(item_types):
368
+ if focus_position < len(item_types): # pylint: disable=consider-using-max-builtin # pylint bug
325
369
  self.focus_position = focus_position
326
370
 
327
371
  @property
@@ -715,7 +759,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
715
759
  w_h_args.append((maxcol, height))
716
760
  elif f == WHSettings.PACK or len(size) == 1:
717
761
  if Sizing.FLOW in w_sizing:
718
- w_h_arg = (maxcol,)
762
+ w_h_arg: tuple[int] | tuple[()] = (maxcol,)
719
763
  elif Sizing.FIXED in w_sizing and f == WHSettings.PACK:
720
764
  w_h_arg = ()
721
765
  else:
@@ -106,7 +106,11 @@ class ProgressBar(Widget):
106
106
  percent = min(100, max(0, int(self.current * 100 / self.done)))
107
107
  return f"{percent!s} %"
108
108
 
109
- def render(self, size: tuple[int], focus: bool = False) -> TextCanvas:
109
+ def render(
110
+ self,
111
+ size: tuple[int], # type: ignore[override]
112
+ focus: bool = False,
113
+ ) -> TextCanvas:
110
114
  """
111
115
  Render the progress bar.
112
116
  """
@@ -43,7 +43,7 @@ if typing.TYPE_CHECKING:
43
43
  __all__ = ("ScrollBar", "Scrollable", "ScrollableError", "ScrollbarSymbols")
44
44
 
45
45
 
46
- WrappedWidget = typing.TypeVar("WrappedWidget")
46
+ WrappedWidget = typing.TypeVar("WrappedWidget", bound="SupportsScroll")
47
47
 
48
48
 
49
49
  class ScrollableError(WidgetError):
@@ -153,7 +153,11 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
153
153
  self.force_forward_keypress = force_forward_keypress
154
154
  super().__init__(widget)
155
155
 
156
- def render(self, size: tuple[int, int], focus: bool = False) -> CompositeCanvas:
156
+ def render(
157
+ self,
158
+ size: tuple[int, int], # type: ignore[override]
159
+ focus: bool = False,
160
+ ) -> CompositeCanvas:
157
161
  from urwid import canvas
158
162
 
159
163
  maxcol, maxrow = size
@@ -270,7 +274,11 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
270
274
 
271
275
  return canv
272
276
 
273
- def keypress(self, size: tuple[int, int], key: str) -> str | None:
277
+ def keypress(
278
+ self,
279
+ size: tuple[int, int], # type: ignore[override]
280
+ key: str,
281
+ ) -> str | None:
274
282
  from urwid.command_map import Command
275
283
 
276
284
  # Maybe offer key to original widget
@@ -311,7 +319,7 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
311
319
 
312
320
  def mouse_event(
313
321
  self,
314
- size: tuple[int, int],
322
+ size: tuple[int, int], # type: ignore[override]
315
323
  event: str,
316
324
  button: int,
317
325
  col: int,
@@ -375,7 +383,10 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
375
383
  elif cursrow >= self._trim_top + maxrow:
376
384
  self._trim_top = max(0, cursrow - maxrow + 1)
377
385
 
378
- def _get_original_widget_size(self, size: tuple[int, int]) -> tuple[int] | tuple[()]:
386
+ def _get_original_widget_size(
387
+ self,
388
+ size: tuple[int, int], # type: ignore[override]
389
+ ) -> tuple[int] | tuple[()]:
379
390
  ow = self._original_widget
380
391
  sizing = ow.sizing()
381
392
  if Sizing.FLOW in sizing:
@@ -465,7 +476,11 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
465
476
  self.scrollbar_width = max(1, width)
466
477
  self._original_widget_size = (0, 0)
467
478
 
468
- def render(self, size: tuple[int, int], focus: bool = False) -> CompositeCanvas:
479
+ def render(
480
+ self,
481
+ size: tuple[int, int], # type: ignore[override]
482
+ focus: bool = False,
483
+ ) -> Canvas:
469
484
  from urwid import canvas
470
485
 
471
486
  maxcol, maxrow = size
@@ -566,12 +581,16 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
566
581
 
567
582
  raise ScrollableError(f"Not compatible to be wrapped by ScrollBar: {w!r}")
568
583
 
569
- def keypress(self, size: tuple[int, int], key: str) -> str | None:
584
+ def keypress(
585
+ self,
586
+ size: tuple[int, int], # type: ignore[override]
587
+ key: str,
588
+ ) -> str | None:
570
589
  return self._original_widget.keypress(self._original_widget_size, key)
571
590
 
572
591
  def mouse_event(
573
592
  self,
574
- size: tuple[int, int],
593
+ size: tuple[int, int], # type: ignore[override]
575
594
  event: str,
576
595
  button: int,
577
596
  col: int,
@@ -580,7 +599,7 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
580
599
  ) -> bool | None:
581
600
  ow = self._original_widget
582
601
  ow_size = self._original_widget_size
583
- handled = False
602
+ handled: bool | None = False
584
603
  if hasattr(ow, "mouse_event"):
585
604
  handled = ow.mouse_event(ow_size, event, button, col, row, focus)
586
605
 
@@ -31,7 +31,11 @@ class SolidFill(Widget):
31
31
  def _repr_words(self) -> list[str]:
32
32
  return [*super()._repr_words(), repr(self.fill_char)]
33
33
 
34
- def render(self, size: tuple[int, int], focus: bool = False) -> SolidCanvas:
34
+ def render(
35
+ self,
36
+ size: tuple[int, int], # type: ignore[override]
37
+ focus: bool = False,
38
+ ) -> SolidCanvas:
35
39
  """
36
40
  Render the Fill as a canvas and return it.
37
41
 
urwid/widget/text.py CHANGED
@@ -72,7 +72,7 @@ class Text(Widget):
72
72
  [('bold', 5)]
73
73
  """
74
74
  super().__init__()
75
- self._cache_maxcol = None
75
+ self._cache_maxcol: int | None = None
76
76
  self.set_text(markup)
77
77
  self.set_layout(align, wrap, layout)
78
78
 
@@ -245,7 +245,11 @@ class Text(Widget):
245
245
  def layout(self):
246
246
  return self._layout
247
247
 
248
- def render(self, size: tuple[int] | tuple[()], focus: bool = False) -> TextCanvas:
248
+ def render(
249
+ self,
250
+ size: tuple[int] | tuple[()], # type: ignore[override]
251
+ focus: bool = False,
252
+ ) -> TextCanvas:
249
253
  """
250
254
  Render contents with wrapping and alignment. Return canvas.
251
255
 
@@ -312,7 +316,11 @@ class Text(Widget):
312
316
  self._cache_maxcol = maxcol
313
317
  self._cache_translation = self.layout.layout(text, maxcol, self._align_mode, self._wrap_mode)
314
318
 
315
- def pack(self, size: tuple[int] | tuple[()] | None = None, focus: bool = False) -> tuple[int, int]:
319
+ def pack(
320
+ self,
321
+ size: tuple[()] | tuple[int] | None = None, # type: ignore[override]
322
+ focus: bool = False,
323
+ ) -> tuple[int, int]:
316
324
  """
317
325
  Return the number of screen columns and rows required for
318
326
  this Text widget to be displayed without wrapping or
@@ -32,26 +32,34 @@ from __future__ import annotations
32
32
 
33
33
  import typing
34
34
 
35
- import urwid
35
+ from .columns import Columns
36
+ from .constants import WHSettings
37
+ from .listbox import ListBox, ListWalker
38
+ from .padding import Padding
39
+ from .text import Text
40
+ from .widget import WidgetWrap
41
+ from .wimp import SelectableIcon
36
42
 
37
43
  if typing.TYPE_CHECKING:
38
44
  from collections.abc import Hashable, Sequence
39
45
 
46
+ __all__ = ("TreeWidgetError", "TreeWidget", "TreeNode", "ParentNode", "TreeWalker", "TreeListBox")
47
+
40
48
 
41
49
  class TreeWidgetError(RuntimeError):
42
50
  pass
43
51
 
44
52
 
45
- class TreeWidget(urwid.WidgetWrap[urwid.Padding]):
53
+ class TreeWidget(WidgetWrap[Padding[typing.Union[Text, Columns]]]):
46
54
  """A widget representing something in a nested tree display."""
47
55
 
48
56
  indent_cols = 3
49
- unexpanded_icon = urwid.SelectableIcon("+", 0)
50
- expanded_icon = urwid.SelectableIcon("-", 0)
57
+ unexpanded_icon = SelectableIcon("+", 0)
58
+ expanded_icon = SelectableIcon("-", 0)
51
59
 
52
60
  def __init__(self, node: TreeNode) -> None:
53
61
  self._node = node
54
- self._innerwidget: urwid.Text | None = None
62
+ self._innerwidget: Text | None = None
55
63
  self.is_leaf = not hasattr(node, "get_first_child")
56
64
  self.expanded = True
57
65
  widget = self.get_indented_widget()
@@ -63,31 +71,32 @@ class TreeWidget(urwid.WidgetWrap[urwid.Padding]):
63
71
  """
64
72
  return not self.is_leaf
65
73
 
66
- def get_indented_widget(self) -> urwid.Padding:
74
+ def get_indented_widget(self) -> Padding[Text | Columns]:
67
75
  widget = self.get_inner_widget()
68
76
  if not self.is_leaf:
69
- widget = urwid.Columns(
77
+ widget = Columns(
70
78
  [(1, [self.unexpanded_icon, self.expanded_icon][self.expanded]), widget],
71
79
  dividechars=1,
72
80
  )
73
81
  indent_cols = self.get_indent_cols()
74
- return urwid.Padding(widget, width=(urwid.RELATIVE, 100), left=indent_cols)
82
+ return Padding(widget, width=(WHSettings.RELATIVE, 100), left=indent_cols)
75
83
 
76
84
  def update_expanded_icon(self) -> None:
77
85
  """Update display widget text for parent widgets"""
78
86
  # icon is first element in columns indented widget
79
- self._w.base_widget.widget_list[0] = [self.unexpanded_icon, self.expanded_icon][self.expanded]
87
+ icon = [self.unexpanded_icon, self.expanded_icon][self.expanded]
88
+ self._w.original_widget.contents[0] = (icon, (WHSettings.GIVEN, 1, False))
80
89
 
81
90
  def get_indent_cols(self) -> int:
82
91
  return self.indent_cols * self.get_node().get_depth()
83
92
 
84
- def get_inner_widget(self) -> urwid.Text:
93
+ def get_inner_widget(self) -> Text:
85
94
  if self._innerwidget is None:
86
95
  self._innerwidget = self.load_inner_widget()
87
96
  return self._innerwidget
88
97
 
89
- def load_inner_widget(self) -> urwid.Widget:
90
- return urwid.Text(self.get_display_text())
98
+ def load_inner_widget(self) -> Text:
99
+ return Text(self.get_display_text())
91
100
 
92
101
  def get_node(self) -> TreeNode:
93
102
  return self._node
@@ -141,7 +150,11 @@ class TreeWidget(urwid.WidgetWrap[urwid.Padding]):
141
150
  prev_node = this_node.get_parent()
142
151
  return prev_node.get_widget()
143
152
 
144
- def keypress(self, size: tuple[int] | tuple[()], key: str) -> str | None:
153
+ def keypress(
154
+ self,
155
+ size: tuple[int] | tuple[()],
156
+ key: str,
157
+ ) -> str | None:
145
158
  """Handle expand & collapse requests (non-leaf nodes)"""
146
159
  if self.is_leaf:
147
160
  return key
@@ -227,7 +240,7 @@ class TreeNode:
227
240
  self._parent = parent
228
241
  self._value = value
229
242
  self._depth = depth
230
- self._widget = None
243
+ self._widget: TreeWidget | None = None
231
244
 
232
245
  def get_widget(self, reload: bool = False) -> TreeWidget:
233
246
  """Return the widget for this node."""
@@ -403,7 +416,7 @@ class ParentNode(TreeNode):
403
416
  return len(self.get_child_keys()) > 0
404
417
 
405
418
 
406
- class TreeWalker(urwid.ListWalker):
419
+ class TreeWalker(ListWalker):
407
420
  """ListWalker-compatible class for displaying TreeWidgets
408
421
 
409
422
  positions are TreeNodes."""
@@ -438,12 +451,16 @@ class TreeWalker(urwid.ListWalker):
438
451
  # pylint: enable=arguments-renamed
439
452
 
440
453
 
441
- class TreeListBox(urwid.ListBox):
454
+ class TreeListBox(ListBox):
442
455
  """A ListBox with special handling for navigation and
443
456
  collapsing of TreeWidgets"""
444
457
 
445
- def keypress(self, size: tuple[int, int], key: str) -> str | None:
446
- key = super().keypress(size, key)
458
+ def keypress(
459
+ self,
460
+ size: tuple[int, int], # type: ignore[override]
461
+ key: str,
462
+ ) -> str | None:
463
+ key: str | None = super().keypress(size, key)
447
464
  return self.unhandled_input(size, key)
448
465
 
449
466
  def unhandled_input(self, size: tuple[int, int], data: str) -> str | None: