urwid 2.4.6__cp37-cp37m-win32.whl → 2.5.1__cp37-cp37m-win32.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.
Files changed (44) hide show
  1. urwid/__init__.py +2 -0
  2. urwid/canvas.py +34 -22
  3. urwid/command_map.py +6 -4
  4. urwid/display/_posix_raw_display.py +15 -4
  5. urwid/display/_raw_display_base.py +37 -37
  6. urwid/display/_win32_raw_display.py +15 -4
  7. urwid/display/curses.py +2 -4
  8. urwid/display/html_fragment.py +2 -4
  9. urwid/display/web.py +2 -4
  10. urwid/listbox.py +111 -26
  11. urwid/monitored_list.py +2 -4
  12. urwid/str_util.pyd +0 -0
  13. urwid/util.py +1 -2
  14. urwid/version.py +2 -2
  15. urwid/vterm.py +3 -3
  16. urwid/widget/__init__.py +4 -0
  17. urwid/widget/attr_map.py +5 -3
  18. urwid/widget/bar_graph.py +9 -9
  19. urwid/widget/box_adapter.py +6 -7
  20. urwid/widget/columns.py +17 -10
  21. urwid/widget/constants.py +169 -63
  22. urwid/widget/divider.py +27 -3
  23. urwid/widget/edit.py +1 -1
  24. urwid/widget/filler.py +9 -8
  25. urwid/widget/frame.py +9 -1
  26. urwid/widget/grid_flow.py +4 -6
  27. urwid/widget/line_box.py +64 -19
  28. urwid/widget/overlay.py +1 -1
  29. urwid/widget/padding.py +11 -8
  30. urwid/widget/pile.py +10 -7
  31. urwid/widget/popup.py +16 -6
  32. urwid/widget/progress_bar.py +13 -6
  33. urwid/widget/scrollable.py +599 -0
  34. urwid/widget/solid_fill.py +3 -1
  35. urwid/widget/text.py +1 -1
  36. urwid/widget/widget.py +51 -113
  37. urwid/widget/widget_decoration.py +21 -12
  38. urwid/widget/wimp.py +12 -20
  39. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/METADATA +7 -2
  40. urwid-2.5.1.dist-info/RECORD +76 -0
  41. urwid-2.4.6.dist-info/RECORD +0 -75
  42. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/COPYING +0 -0
  43. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/WHEEL +0 -0
  44. {urwid-2.4.6.dist-info → urwid-2.5.1.dist-info}/top_level.txt +0 -0
urwid/listbox.py CHANGED
@@ -25,6 +25,8 @@ import warnings
25
25
  from collections.abc import Iterable, Sized
26
26
  from contextlib import suppress
27
27
 
28
+ from typing_extensions import Protocol, runtime_checkable
29
+
28
30
  from urwid import signals
29
31
  from urwid.canvas import CanvasCombine, SolidCanvas
30
32
  from urwid.command_map import Command
@@ -50,12 +52,30 @@ if typing.TYPE_CHECKING:
50
52
  from urwid.canvas import CompositeCanvas
51
53
 
52
54
  _T = typing.TypeVar("_T")
55
+ _K = typing.TypeVar("_K")
53
56
 
54
57
 
55
58
  class ListWalkerError(Exception):
56
59
  pass
57
60
 
58
61
 
62
+ @runtime_checkable
63
+ class ScrollSupportingBody(Sized, Protocol):
64
+ """Protocol for ListWalkers that support Scrolling."""
65
+
66
+ def get_focus(self) -> tuple[Widget, _K]: ...
67
+
68
+ def set_focus(self, position: _K) -> None: ...
69
+
70
+ def __getitem__(self, index: _K) -> _T: ...
71
+
72
+ def positions(self, reverse: bool = False) -> Iterable[_K]: ...
73
+
74
+ def get_next(self, position: _K) -> tuple[Widget, _K] | tuple[None, None]: ...
75
+
76
+ def get_prev(self, position: _K) -> tuple[Widget, _K] | tuple[None, None]: ...
77
+
78
+
59
79
  class ListWalker(metaclass=signals.MetaSignals):
60
80
  signals: typing.ClassVar[list[str]] = ["modified"]
61
81
 
@@ -262,6 +282,8 @@ class ListBoxError(Exception):
262
282
 
263
283
 
264
284
  class _Middle(typing.NamedTuple):
285
+ """Named tuple for ListBox internals."""
286
+
265
287
  offset: int
266
288
  focus_widget: Widget
267
289
  focus_pos: Hashable
@@ -269,20 +291,30 @@ class _Middle(typing.NamedTuple):
269
291
  cursor: tuple[int, int] | tuple[int] | None
270
292
 
271
293
 
294
+ class _FillItem(typing.NamedTuple):
295
+ """Named tuple for ListBox internals."""
296
+
297
+ widget: Widget
298
+ position: Hashable
299
+ rows: int
300
+
301
+
272
302
  class _TopBottom(typing.NamedTuple):
303
+ """Named tuple for ListBox internals."""
304
+
273
305
  trim: int
274
- fill: list[tuple[Widget, Hashable, int]]
306
+ fill: list[_FillItem]
275
307
 
276
308
 
277
309
  class ListBox(Widget, WidgetContainerMixin):
278
310
  """
279
- a horizontally stacked list of widgets
311
+ Vertically stacked list of widgets
280
312
  """
281
313
 
282
314
  _selectable = True
283
315
  _sizing = frozenset([Sizing.BOX])
284
316
 
285
- def __init__(self, body: ListWalker) -> None:
317
+ def __init__(self, body: ListWalker | Iterable[Widget]) -> None:
286
318
  """
287
319
  :param body: a ListWalker subclass such as :class:`SimpleFocusListWalker`
288
320
  that contains widgets to be displayed inside the list box
@@ -314,6 +346,10 @@ class ListBox(Widget, WidgetContainerMixin):
314
346
  # variable for delayed valign change used by set_focus_valign
315
347
  self.set_focus_valign_pending = None
316
348
 
349
+ # used for scrollable protocol
350
+ self._rows_max_cached = 0
351
+ self._rendered_size = 0, 0
352
+
317
353
  @property
318
354
  def body(self) -> ListWalker:
319
355
  """
@@ -323,7 +359,7 @@ class ListBox(Widget, WidgetContainerMixin):
323
359
  return self._body
324
360
 
325
361
  @body.setter
326
- def body(self, body):
362
+ def body(self, body: Iterable[Widget] | ListWalker) -> None:
327
363
  with suppress(AttributeError):
328
364
  disconnect_signal(self._body, "modified", self._invalidate)
329
365
  # _body may be not yet assigned
@@ -383,12 +419,10 @@ class ListBox(Widget, WidgetContainerMixin):
383
419
  *cursor coords* or ``None``)
384
420
  *top*
385
421
  (*# lines to trim off top*,
386
- list of (*widget*, *position*, *rows*) tuples above focus
387
- in order from bottom to top)
422
+ list of (*widget*, *position*, *rows*) tuples above focus in order from bottom to top)
388
423
  *bottom*
389
424
  (*# lines to trim off bottom*,
390
- list of (*widget*, *position*, *rows*) tuples below focus
391
- in order from top to bottom)
425
+ list of (*widget*, *position*, *rows*) tuples below focus in order from top to bottom)
392
426
  """
393
427
  (maxcol, maxrow) = size
394
428
 
@@ -441,7 +475,7 @@ class ListBox(Widget, WidgetContainerMixin):
441
475
 
442
476
  p_rows = prev.rows((maxcol,))
443
477
  if p_rows: # filter out 0-height widgets
444
- fill_above.append((prev, pos, p_rows))
478
+ fill_above.append(_FillItem(prev, pos, p_rows))
445
479
  if p_rows > fill_lines: # crosses top edge?
446
480
  trim_top = p_rows - fill_lines
447
481
  break
@@ -462,7 +496,7 @@ class ListBox(Widget, WidgetContainerMixin):
462
496
 
463
497
  n_rows = next_pos.rows((maxcol,))
464
498
  if n_rows: # filter out 0-height widgets
465
- fill_below.append((next_pos, pos, n_rows))
499
+ fill_below.append(_FillItem(next_pos, pos, n_rows))
466
500
  if n_rows > fill_lines: # crosses bottom edge?
467
501
  trim_bottom = n_rows - fill_lines
468
502
  fill_lines -= n_rows
@@ -488,7 +522,7 @@ class ListBox(Widget, WidgetContainerMixin):
488
522
  break
489
523
 
490
524
  p_rows = prev.rows((maxcol,))
491
- fill_above.append((prev, pos, p_rows))
525
+ fill_above.append(_FillItem(prev, pos, p_rows))
492
526
  if p_rows > fill_lines: # more than required
493
527
  trim_top = p_rows - fill_lines
494
528
  offset_rows += fill_lines
@@ -503,6 +537,54 @@ class ListBox(Widget, WidgetContainerMixin):
503
537
  _TopBottom(trim_bottom, fill_below),
504
538
  )
505
539
 
540
+ def get_scrollpos(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
541
+ """Current scrolling position."""
542
+ if not isinstance(self._body, ScrollSupportingBody):
543
+ raise ListBoxError(f"{self} body do not implement methods required for scrolling protocol")
544
+
545
+ if not self._body:
546
+ return 0
547
+
548
+ if size is not None:
549
+ self._rendered_size = size
550
+
551
+ _, focus_pos = self._body.get_focus()
552
+
553
+ mid, top, bottom = self.calculate_visible(self._rendered_size, focus)
554
+
555
+ start_row = top.trim
556
+ maxcol = self._rendered_size[0]
557
+
558
+ if top.fill:
559
+ pos = top.fill[-1].position
560
+ else:
561
+ pos = mid.focus_pos
562
+
563
+ prev, pos = self._body.get_prev(pos)
564
+ while prev is not None:
565
+ start_row += prev.rows((maxcol,))
566
+ prev, pos = self._body.get_prev(pos)
567
+
568
+ return start_row
569
+
570
+ def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int:
571
+ """Scrollable protocol for sized iterable and not wrapped around contents."""
572
+ if not isinstance(self._body, ScrollSupportingBody):
573
+ raise ListBoxError(f"{self} body do not implement methods required for scrolling protocol")
574
+
575
+ if getattr(self._body, "wrap_around", False):
576
+ raise ListBoxError("Body is wrapped around")
577
+
578
+ if size is not None:
579
+ self._rendered_size = size
580
+
581
+ if size or not self._rows_max_cached:
582
+ self._rows_max_cached = sum(
583
+ self._body[position].rows((self._rendered_size[0],), focus) for position in self._body.positions()
584
+ )
585
+
586
+ return self._rows_max_cached
587
+
506
588
  def render(self, size: tuple[int, int], focus: bool = False) -> CompositeCanvas | SolidCanvas:
507
589
  """
508
590
  Render ListBox and return canvas.
@@ -511,6 +593,8 @@ class ListBox(Widget, WidgetContainerMixin):
511
593
  """
512
594
  (maxcol, maxrow) = size
513
595
 
596
+ self._rendered_size = size
597
+
514
598
  middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=focus)
515
599
  if middle is None:
516
600
  return SolidCanvas(" ", maxcol, maxrow)
@@ -615,9 +699,7 @@ class ListBox(Widget, WidgetContainerMixin):
615
699
  ):
616
700
  """Set the focus widget's display offset and inset.
617
701
 
618
- :param valign: one of:
619
- 'top', 'middle', 'bottom'
620
- ('relative', percentage 0=top 100=bottom)
702
+ :param valign: one of: 'top', 'middle', 'bottom' ('relative', percentage 0=top 100=bottom)
621
703
  """
622
704
  vt, va = normalize_valign(valign, ListBoxError)
623
705
  self.set_focus_valign_pending = vt, va
@@ -756,10 +838,7 @@ class ListBox(Widget, WidgetContainerMixin):
756
838
  """
757
839
 
758
840
  def _set_focus_valign_complete(self, size: tuple[int, int], focus: bool) -> None:
759
- """
760
- Finish setting the offset and inset now that we have have a
761
- maxcol & maxrow.
762
- """
841
+ """Finish setting the offset and inset now that we have have a maxcol & maxrow."""
763
842
  (maxcol, maxrow) = size
764
843
  vt, va = self.set_focus_valign_pending
765
844
  self.set_focus_valign_pending = None
@@ -784,10 +863,7 @@ class ListBox(Widget, WidgetContainerMixin):
784
863
  self.shift_focus((maxcol, maxrow), rtop)
785
864
 
786
865
  def _set_focus_first_selectable(self, size: tuple[int, int], focus: bool) -> None:
787
- """
788
- Choose the first visible, selectable widget below the
789
- current focus as the focus widget.
790
- """
866
+ """Choose the first visible, selectable widget below the current focus as the focus widget."""
791
867
  (maxcol, maxrow) = size
792
868
  self.set_focus_valign_pending = None
793
869
  self.set_focus_pending = None
@@ -813,9 +889,7 @@ class ListBox(Widget, WidgetContainerMixin):
813
889
  new_row_offset += rows
814
890
 
815
891
  def _set_focus_complete(self, size: tuple[int, int], focus: bool) -> None:
816
- """
817
- Finish setting the position now that we have maxcol & maxrow.
818
- """
892
+ """Finish setting the position now that we have maxcol & maxrow."""
819
893
  (maxcol, maxrow) = size
820
894
  self._invalidate()
821
895
  if self.set_focus_pending == "first selectable":
@@ -1716,7 +1790,18 @@ class ListBox(Widget, WidgetContainerMixin):
1716
1790
  )
1717
1791
  return False
1718
1792
 
1719
- return w.mouse_event((maxcol,), event, button, col, row - wrow, focus)
1793
+ handled = w.mouse_event((maxcol,), event, button, col, row - wrow, focus)
1794
+ if handled:
1795
+ return True
1796
+
1797
+ if is_mouse_press(event):
1798
+ if button == 4:
1799
+ return not self._keypress_up((maxcol, maxrow))
1800
+
1801
+ if button == 5:
1802
+ return not self._keypress_down((maxcol, maxrow))
1803
+
1804
+ return False
1720
1805
 
1721
1806
  def ends_visible(self, size: tuple[int, int], focus: bool = False) -> list[Literal["top", "bottom"]]:
1722
1807
  """
urwid/monitored_list.py CHANGED
@@ -337,12 +337,10 @@ class MonitoredFocusList(MonitoredList[_T], typing.Generic[_T]):
337
337
  self.focus = focus
338
338
 
339
339
  @typing.overload
340
- def __setitem__(self, i: int, y: _T) -> None:
341
- ...
340
+ def __setitem__(self, i: int, y: _T) -> None: ...
342
341
 
343
342
  @typing.overload
344
- def __setitem__(self, i: slice, y: Collection[_T]) -> None:
345
- ...
343
+ def __setitem__(self, i: slice, y: Collection[_T]) -> None: ...
346
344
 
347
345
  def __setitem__(self, i: int | slice, y: _T | Collection[_T]) -> None:
348
346
  """
urwid/str_util.pyd CHANGED
Binary file
urwid/util.py CHANGED
@@ -34,8 +34,7 @@ if typing.TYPE_CHECKING:
34
34
  from typing_extensions import Protocol, Self
35
35
 
36
36
  class CanBeStopped(Protocol):
37
- def stop(self) -> None:
38
- ...
37
+ def stop(self) -> None: ...
39
38
 
40
39
 
41
40
  try:
urwid/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '2.4.6'
4
- __version_tuple__ = version_tuple = (2, 4, 6)
3
+ __version__ = version = '2.5.1'
4
+ __version_tuple__ = version_tuple = (2, 5, 1)
urwid/vterm.py CHANGED
@@ -1437,11 +1437,11 @@ class TermCanvas(Canvas):
1437
1437
  def content(
1438
1438
  self,
1439
1439
  trim_left: int = 0,
1440
- trim_right: int = 0,
1440
+ trim_top: int = 0,
1441
1441
  cols: int | None = None,
1442
1442
  rows: int | None = None,
1443
1443
  attr=None,
1444
- ):
1444
+ ) -> Iterable[list[tuple[object, object, bytes]]]:
1445
1445
  if self.scrolling_up == 0:
1446
1446
  yield from self.term
1447
1447
  else:
@@ -1669,7 +1669,7 @@ class Terminal(Widget):
1669
1669
  elif hasattr(self, "old_tios"):
1670
1670
  RealTerminal().tty_signal_keys(*self.old_tios)
1671
1671
 
1672
- def render(self, size: tuple[int, int], focus: bool = False):
1672
+ def render(self, size: tuple[int, int], focus: bool = False) -> TermCanvas:
1673
1673
  if not self.terminated:
1674
1674
  self.change_focus(focus)
1675
1675
 
urwid/widget/__init__.py CHANGED
@@ -34,6 +34,7 @@ from .padding import Padding, PaddingError, PaddingWarning, calculate_left_right
34
34
  from .pile import Pile, PileError, PileWarning
35
35
  from .popup import PopUpLauncher, PopUpTarget
36
36
  from .progress_bar import ProgressBar
37
+ from .scrollable import Scrollable, ScrollableError, ScrollBar
37
38
  from .solid_fill import SolidFill
38
39
  from .text import Text, TextError
39
40
  from .widget import (
@@ -151,6 +152,9 @@ __all__ = (
151
152
  "ProgressBar",
152
153
  "WidgetContainerListContentsMixin",
153
154
  "WidgetWarning",
155
+ "Scrollable",
156
+ "ScrollableError",
157
+ "ScrollBar",
154
158
  )
155
159
 
156
160
  # Backward compatibility
urwid/widget/attr_map.py CHANGED
@@ -5,22 +5,24 @@ from collections.abc import Hashable, Mapping
5
5
 
6
6
  from urwid.canvas import CompositeCanvas
7
7
 
8
- from .widget import Widget, WidgetError, delegate_to_widget_mixin
8
+ from .widget import WidgetError, delegate_to_widget_mixin
9
9
  from .widget_decoration import WidgetDecoration
10
10
 
11
+ WrappedWidget = typing.TypeVar("WrappedWidget")
12
+
11
13
 
12
14
  class AttrMapError(WidgetError):
13
15
  pass
14
16
 
15
17
 
16
- class AttrMap(delegate_to_widget_mixin("_original_widget"), WidgetDecoration):
18
+ class AttrMap(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[WrappedWidget]):
17
19
  """
18
20
  AttrMap is a decoration that maps one set of attributes to another.
19
21
  This object will pass all function calls and variable references to the
20
22
  wrapped widget.
21
23
  """
22
24
 
23
- def __init__(self, w: Widget, attr_map, focus_map=None) -> None:
25
+ def __init__(self, w: WrappedWidget, attr_map, focus_map=None) -> None:
24
26
  """
25
27
  :param w: widget to wrap (stored as self.original_widget)
26
28
  :type w: widget
urwid/widget/bar_graph.py CHANGED
@@ -5,7 +5,7 @@ import typing
5
5
  from urwid.canvas import CanvasCombine, CompositeCanvas, SolidCanvas
6
6
  from urwid.util import get_encoding_mode
7
7
 
8
- from .constants import Sizing
8
+ from .constants import BAR_SYMBOLS, Sizing
9
9
  from .text import Text
10
10
  from .widget import Widget, WidgetError, WidgetMeta, nocache_widget_render, nocache_widget_render_instance
11
11
 
@@ -49,10 +49,10 @@ class BarGraph(Widget, metaclass=BarGraphMeta):
49
49
 
50
50
  ignore_focus = True
51
51
 
52
- eighths = " ▁▂▃▄▅▆▇"
52
+ eighths = BAR_SYMBOLS.VERTICAL[:8] # Full height is done by style
53
53
  hlines = "_⎺⎻─⎼⎽"
54
54
 
55
- def __init__(self, attlist, hatt=None, satt=None):
55
+ def __init__(self, attlist, hatt=None, satt=None) -> None:
56
56
  """
57
57
  Create a bar graph with the passed display characteristics.
58
58
  see set_segment_attributes for a description of the parameters.
@@ -134,7 +134,7 @@ class BarGraph(Widget, metaclass=BarGraphMeta):
134
134
  raise BarGraphError(f"fg ({fg}) not > bg ({bg})")
135
135
  self.satt = satt
136
136
 
137
- def set_data(self, bardata, top, hlines=None):
137
+ def set_data(self, bardata, top: float, hlines=None) -> None:
138
138
  """
139
139
  Store bar data, bargraph top and horizontal line positions.
140
140
 
@@ -428,7 +428,7 @@ class BarGraph(Widget, metaclass=BarGraphMeta):
428
428
  return canv
429
429
 
430
430
 
431
- def calculate_bargraph_display(bardata, top, bar_widths, maxrow: int):
431
+ def calculate_bargraph_display(bardata, top: float, bar_widths: list[int], maxrow: int):
432
432
  """
433
433
  Calculate a rendering of the bar graph described by data, bar_widths
434
434
  and height.
@@ -574,7 +574,7 @@ def calculate_bargraph_display(bardata, top, bar_widths, maxrow: int):
574
574
  class GraphVScale(Widget):
575
575
  _sizing = frozenset([Sizing.BOX])
576
576
 
577
- def __init__(self, labels, top):
577
+ def __init__(self, labels, top: float) -> None:
578
578
  """
579
579
  GraphVScale( [(label1 position, label1 markup),...], top )
580
580
  label position -- 0 < position < top for the y position
@@ -587,7 +587,7 @@ class GraphVScale(Widget):
587
587
  super().__init__()
588
588
  self.set_scale(labels, top)
589
589
 
590
- def set_scale(self, labels, top):
590
+ def set_scale(self, labels, top: float) -> None:
591
591
  """
592
592
  set_scale( [(label1 position, label1 markup),...], top )
593
593
  label position -- 0 < position < top for the y position
@@ -610,7 +610,7 @@ class GraphVScale(Widget):
610
610
  """
611
611
  return False
612
612
 
613
- def render(self, size: tuple[int, int], focus: bool = False):
613
+ def render(self, size: tuple[int, int], focus: bool = False) -> SolidCanvas | CompositeCanvas:
614
614
  """
615
615
  Render GraphVScale.
616
616
  """
@@ -641,7 +641,7 @@ class GraphVScale(Widget):
641
641
  return c
642
642
 
643
643
 
644
- def scale_bar_values(bar, top, maxrow: int):
644
+ def scale_bar_values(bar, top: float, maxrow: int) -> list[int]:
645
645
  """
646
646
  Return a list of bar values aliased to integer values of maxrow.
647
647
  """
@@ -8,22 +8,21 @@ from urwid.canvas import CompositeCanvas
8
8
  from .constants import Sizing
9
9
  from .widget_decoration import WidgetDecoration, WidgetError
10
10
 
11
- if typing.TYPE_CHECKING:
12
- from .widget import Widget
11
+ WrappedWidget = typing.TypeVar("WrappedWidget")
13
12
 
14
13
 
15
14
  class BoxAdapterError(WidgetError):
16
15
  pass
17
16
 
18
17
 
19
- class BoxAdapter(WidgetDecoration):
18
+ class BoxAdapter(WidgetDecoration[WrappedWidget]):
20
19
  """
21
20
  Adapter for using a box widget where a flow widget would usually go
22
21
  """
23
22
 
24
23
  no_cache: typing.ClassVar[list[str]] = ["rows"]
25
24
 
26
- def __init__(self, box_widget, height):
25
+ def __init__(self, box_widget: WrappedWidget, height: int) -> None:
27
26
  """
28
27
  Create a flow widget that contains a box widget
29
28
 
@@ -47,7 +46,7 @@ class BoxAdapter(WidgetDecoration):
47
46
 
48
47
  # originally stored as box_widget, keep for compatibility
49
48
  @property
50
- def box_widget(self) -> Widget:
49
+ def box_widget(self) -> WrappedWidget:
51
50
  warnings.warn(
52
51
  "original stored as original_widget, keep for compatibility",
53
52
  PendingDeprecationWarning,
@@ -56,7 +55,7 @@ class BoxAdapter(WidgetDecoration):
56
55
  return self.original_widget
57
56
 
58
57
  @box_widget.setter
59
- def box_widget(self, widget: Widget):
58
+ def box_widget(self, widget: WrappedWidget) -> None:
60
59
  warnings.warn(
61
60
  "original stored as original_widget, keep for compatibility",
62
61
  PendingDeprecationWarning,
@@ -104,7 +103,7 @@ class BoxAdapter(WidgetDecoration):
104
103
  def mouse_event(
105
104
  self,
106
105
  size: tuple[int],
107
- event,
106
+ event: str,
108
107
  button: int,
109
108
  col: int,
110
109
  row: int,
urwid/widget/columns.py CHANGED
@@ -6,6 +6,7 @@ from itertools import chain, repeat
6
6
 
7
7
  import urwid
8
8
  from urwid.canvas import Canvas, CanvasJoin, CompositeCanvas, SolidCanvas
9
+ from urwid.command_map import Command
9
10
  from urwid.monitored_list import MonitoredFocusList, MonitoredList
10
11
  from urwid.util import is_mouse_press
11
12
 
@@ -455,9 +456,9 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
455
456
 
456
457
  @staticmethod
457
458
  def options(
458
- width_type: (
459
- Literal["pack", "given", "weight", WHSettings.PACK, WHSettings.GIVEN, WHSettings.WEIGHT]
460
- ) = WHSettings.WEIGHT,
459
+ width_type: Literal[
460
+ "pack", "given", "weight", WHSettings.PACK, WHSettings.GIVEN, WHSettings.WEIGHT
461
+ ] = WHSettings.WEIGHT,
461
462
  width_amount: int | None = 1,
462
463
  box_widget: bool = False,
463
464
  ) -> tuple[Literal[WHSettings.PACK], None, bool] | tuple[Literal[WHSettings.GIVEN, WHSettings.WEIGHT], int, bool]:
@@ -795,13 +796,16 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
795
796
  else:
796
797
  w_h_args[i] = (0,)
797
798
 
798
- elif Sizing.FIXED in w_sizing and (Sizing.FLOW in w_sizing or is_box):
799
- width, height = widget.pack((), focused)
799
+ elif Sizing.FLOW in w_sizing or is_box:
800
+ if Sizing.FIXED in w_sizing:
801
+ width, height = widget.pack((), focused)
802
+ else:
803
+ width = self.min_width
804
+
800
805
  weighted.setdefault(size_weight, []).append((widget, i, is_box, focused))
801
806
  weights.append(size_weight)
802
807
  weight_max_sizes.setdefault(size_weight, width)
803
808
  weight_max_sizes[size_weight] = max(weight_max_sizes[size_weight], width)
804
-
805
809
  else:
806
810
  raise ColumnsError(f"Unsupported combination of {size_kind} box={is_box!r} for {widget}")
807
811
 
@@ -819,6 +823,9 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
819
823
  else:
820
824
  box.append(i)
821
825
 
826
+ if not heights:
827
+ raise ColumnsError(f"No height information for pack {self!r} as FIXED")
828
+
822
829
  max_height = max(heights.values())
823
830
  for idx in box:
824
831
  heights[idx] = max_height
@@ -1019,7 +1026,7 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
1019
1026
  def mouse_event(
1020
1027
  self,
1021
1028
  size: tuple[()] | tuple[int] | tuple[int, int],
1022
- event,
1029
+ event: str,
1023
1030
  button: int,
1024
1031
  col: int,
1025
1032
  row: int,
@@ -1115,15 +1122,15 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
1115
1122
 
1116
1123
  i = self.focus_position
1117
1124
  w, _ = self.contents[i]
1118
- if self._command_map[key] not in {"cursor up", "cursor down", "cursor page up", "cursor page down"}:
1125
+ if self._command_map[key] not in {Command.UP, Command.DOWN, Command.PAGE_UP, Command.PAGE_DOWN}:
1119
1126
  self.pref_col = None
1120
1127
  if w.selectable():
1121
1128
  key = w.keypress(size_args[i], key)
1122
1129
 
1123
- if self._command_map[key] not in {"cursor left", "cursor right"}:
1130
+ if self._command_map[key] not in {Command.LEFT, Command.RIGHT}:
1124
1131
  return key
1125
1132
 
1126
- if self._command_map[key] == "cursor left":
1133
+ if self._command_map[key] == Command.LEFT:
1127
1134
  candidates = list(range(i - 1, -1, -1)) # count backwards to 0
1128
1135
  else: # key == 'right'
1129
1136
  candidates = list(range(i + 1, len(self.contents)))