urwid 2.6.15__py3-none-any.whl → 3.0.0__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 (54) hide show
  1. urwid/__init__.py +1 -4
  2. urwid/canvas.py +27 -46
  3. urwid/command_map.py +6 -4
  4. urwid/container.py +1 -1
  5. urwid/decoration.py +1 -1
  6. urwid/display/_posix_raw_display.py +16 -4
  7. urwid/display/_raw_display_base.py +8 -5
  8. urwid/display/_win32_raw_display.py +16 -17
  9. urwid/display/common.py +26 -55
  10. urwid/display/curses.py +1 -1
  11. urwid/display/escape.py +15 -12
  12. urwid/display/lcd.py +4 -6
  13. urwid/display/web.py +7 -12
  14. urwid/event_loop/asyncio_loop.py +1 -2
  15. urwid/event_loop/main_loop.py +13 -18
  16. urwid/event_loop/tornado_loop.py +4 -5
  17. urwid/event_loop/trio_loop.py +1 -1
  18. urwid/font.py +13 -18
  19. urwid/signals.py +2 -1
  20. urwid/str_util.py +15 -18
  21. urwid/text_layout.py +6 -7
  22. urwid/util.py +7 -18
  23. urwid/version.py +9 -4
  24. urwid/vterm.py +18 -45
  25. urwid/widget/__init__.py +0 -6
  26. urwid/widget/attr_wrap.py +8 -10
  27. urwid/widget/bar_graph.py +3 -8
  28. urwid/widget/big_text.py +9 -7
  29. urwid/widget/box_adapter.py +4 -4
  30. urwid/widget/columns.py +52 -83
  31. urwid/widget/container.py +29 -75
  32. urwid/widget/edit.py +8 -8
  33. urwid/widget/filler.py +6 -6
  34. urwid/widget/frame.py +28 -37
  35. urwid/widget/grid_flow.py +25 -110
  36. urwid/widget/line_box.py +13 -0
  37. urwid/widget/listbox.py +12 -50
  38. urwid/widget/monitored_list.py +6 -4
  39. urwid/widget/overlay.py +4 -37
  40. urwid/widget/padding.py +11 -48
  41. urwid/widget/pile.py +179 -158
  42. urwid/widget/popup.py +2 -2
  43. urwid/widget/progress_bar.py +10 -11
  44. urwid/widget/scrollable.py +25 -33
  45. urwid/widget/treetools.py +27 -48
  46. urwid/widget/widget.py +7 -124
  47. urwid/widget/widget_decoration.py +4 -33
  48. urwid/wimp.py +1 -1
  49. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info}/METADATA +18 -18
  50. urwid-3.0.0.dist-info/RECORD +74 -0
  51. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info}/WHEEL +1 -1
  52. urwid-2.6.15.dist-info/RECORD +0 -74
  53. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info/licenses}/COPYING +0 -0
  54. {urwid-2.6.15.dist-info → urwid-3.0.0.dist-info}/top_level.txt +0 -0
urwid/widget/padding.py CHANGED
@@ -215,24 +215,6 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
215
215
  self._align_type, self._align_amount = normalize_align(align, PaddingError)
216
216
  self._invalidate()
217
217
 
218
- def _get_align(self) -> Literal["left", "center", "right"] | tuple[Literal["relative"], int]:
219
- warnings.warn(
220
- f"Method `{self.__class__.__name__}._get_align` is deprecated, "
221
- f"please use property `{self.__class__.__name__}.align`",
222
- DeprecationWarning,
223
- stacklevel=2,
224
- )
225
- return self.align
226
-
227
- def _set_align(self, align: Literal["left", "center", "right"] | tuple[Literal["relative"], int]) -> None:
228
- warnings.warn(
229
- f"Method `{self.__class__.__name__}._set_align` is deprecated, "
230
- f"please use property `{self.__class__.__name__}.align`",
231
- DeprecationWarning,
232
- stacklevel=2,
233
- )
234
- self.align = align
235
-
236
218
  @property
237
219
  def width(
238
220
  self,
@@ -261,24 +243,6 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
261
243
  self._width_type, self._width_amount = normalize_width(width, PaddingError)
262
244
  self._invalidate()
263
245
 
264
- def _get_width(self) -> Literal["clip", "pack"] | int | tuple[Literal["relative"], int]:
265
- warnings.warn(
266
- f"Method `{self.__class__.__name__}._get_width` is deprecated, "
267
- f"please use property `{self.__class__.__name__}.width`",
268
- DeprecationWarning,
269
- stacklevel=2,
270
- )
271
- return self.width
272
-
273
- def _set_width(self, width: Literal["clip", "pack"] | int | tuple[Literal["relative"], int]) -> None:
274
- warnings.warn(
275
- f"Method `{self.__class__.__name__}._set_width` is deprecated, "
276
- f"please use property `{self.__class__.__name__}.width`",
277
- DeprecationWarning,
278
- stacklevel=2,
279
- )
280
- self.width = width
281
-
282
246
  def pack(
283
247
  self,
284
248
  size: tuple[()] | tuple[int] | tuple[int, int] = (),
@@ -339,9 +303,9 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
339
303
  PaddingWarning,
340
304
  stacklevel=3,
341
305
  )
342
- canv = self._original_widget.render((maxcol,) + size[1:], focus)
306
+ canv = self._original_widget.render((maxcol, *size[1:]), focus)
343
307
  elif self._width_type == WHSettings.GIVEN:
344
- canv = self._original_widget.render((self._width_amount,) + size[1:], focus)
308
+ canv = self._original_widget.render((self._width_amount, *size[1:]), focus)
345
309
  else:
346
310
  canv = self._original_widget.render((), focus)
347
311
 
@@ -439,7 +403,7 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
439
403
  """Pass keypress to self._original_widget."""
440
404
  left, right = self.padding_values(size, True)
441
405
  if size:
442
- maxvals = (size[0] - left - right,) + size[1:]
406
+ maxvals = (size[0] - left - right, *size[1:])
443
407
  return self._original_widget.keypress(maxvals, key)
444
408
  return self._original_widget.keypress((), key)
445
409
 
@@ -450,18 +414,17 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
450
414
 
451
415
  left, right = self.padding_values(size, True)
452
416
  if size:
453
- maxvals = (size[0] - left - right,) + size[1:]
417
+ maxvals = (size[0] - left - right, *size[1:])
454
418
  if maxvals[0] == 0:
455
419
  return None
456
420
  else:
457
421
  maxvals = ()
458
422
 
459
- coords = self._original_widget.get_cursor_coords(maxvals)
460
- if coords is None:
461
- return None
423
+ if (coords := self._original_widget.get_cursor_coords(maxvals)) is not None:
424
+ x, y = coords
425
+ return x + left, y
462
426
 
463
- x, y = coords
464
- return x + left, y
427
+ return None
465
428
 
466
429
  def move_cursor_to_coords(
467
430
  self,
@@ -479,7 +442,7 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
479
442
  left, right = self.padding_values(size, True)
480
443
  if size:
481
444
  maxcol = size[0]
482
- maxvals = (maxcol - left - right,) + size[1:]
445
+ maxvals = (maxcol - left - right, *size[1:])
483
446
  else:
484
447
  maxcol = self.pack((), True)[0]
485
448
  maxvals = ()
@@ -511,7 +474,7 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
511
474
  maxcol = size[0]
512
475
  if col < left or col >= maxcol - right:
513
476
  return False
514
- maxvals = (maxcol - left - right,) + size[1:]
477
+ maxvals = (maxcol - left - right, *size[1:])
515
478
  else:
516
479
  maxvals = ()
517
480
 
@@ -524,7 +487,7 @@ class Padding(WidgetDecoration[WrappedWidget], typing.Generic[WrappedWidget]):
524
487
 
525
488
  left, right = self.padding_values(size, True)
526
489
  if size:
527
- maxvals = (size[0] - left - right,) + size[1:]
490
+ maxvals = (size[0] - left - right, *size[1:])
528
491
  else:
529
492
  maxvals = ()
530
493
 
urwid/widget/pile.py CHANGED
@@ -135,8 +135,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
135
135
 
136
136
  if not flag & box_flow_fixed:
137
137
  warnings.warn(
138
- f"Sizing combination of widget {idx} not supported: "
139
- f"{size_kind.name} {'|'.join(w_sizing).upper()}",
138
+ f"Sizing combination of widget {idx} not supported: {size_kind.name} {'|'.join(w_sizing).upper()}",
140
139
  PileWarning,
141
140
  stacklevel=3,
142
141
  )
@@ -302,8 +301,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
302
301
  standard container property :attr:`contents`.
303
302
  """
304
303
  warnings.warn(
305
- "only for backwards compatibility. You should use the new standard container property `contents`",
306
- PendingDeprecationWarning,
304
+ "only for backwards compatibility. You should use the new standard container property `contents`."
305
+ "API will be removed in version 5.0.",
306
+ DeprecationWarning,
307
307
  stacklevel=2,
308
308
  )
309
309
  ml = MonitoredList(w for w, t in self.contents)
@@ -325,7 +325,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
325
325
  chain(self.contents, repeat((None, (WHSettings.WEIGHT, 1)))),
326
326
  )
327
327
  ]
328
- if focus_position < len(widgets): # pylint: disable=consider-using-max-builtin # pylint bug
328
+ if focus_position < len(widgets):
329
329
  self.focus_position = focus_position
330
330
 
331
331
  @property
@@ -337,8 +337,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
337
337
  standard container property :attr:`contents`.
338
338
  """
339
339
  warnings.warn(
340
- "only for backwards compatibility. You should use the new standard container property `contents`",
341
- PendingDeprecationWarning,
340
+ "only for backwards compatibility. You should use the new standard container property `contents`."
341
+ "API will be removed in version 5.0.",
342
+ DeprecationWarning,
342
343
  stacklevel=2,
343
344
  )
344
345
  ml = MonitoredList(
@@ -356,8 +357,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
356
357
  @item_types.setter
357
358
  def item_types(self, item_types):
358
359
  warnings.warn(
359
- "only for backwards compatibility. You should use the new standard container property `contents`",
360
- PendingDeprecationWarning,
360
+ "only for backwards compatibility. You should use the new standard container property `contents`."
361
+ "API will be removed in version 5.0.",
362
+ DeprecationWarning,
361
363
  stacklevel=2,
362
364
  )
363
365
  focus_position = self.focus_position
@@ -365,7 +367,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
365
367
  (w, ({Sizing.FIXED: WHSettings.GIVEN, Sizing.FLOW: WHSettings.PACK}.get(new_t, new_t), new_height))
366
368
  for ((new_t, new_height), (w, options)) in zip(item_types, self.contents)
367
369
  ]
368
- if focus_position < len(item_types): # pylint: disable=consider-using-max-builtin # pylint bug
370
+ if focus_position < len(item_types):
369
371
  self.focus_position = focus_position
370
372
 
371
373
  @property
@@ -468,17 +470,6 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
468
470
  return
469
471
  raise ValueError(f"Widget not found in Pile contents: {item!r}")
470
472
 
471
- def _get_focus(self) -> Widget:
472
- warnings.warn(
473
- f"method `{self.__class__.__name__}._get_focus` is deprecated, "
474
- f"please use `{self.__class__.__name__}.focus` property",
475
- DeprecationWarning,
476
- stacklevel=3,
477
- )
478
- if not self.contents:
479
- return None
480
- return self.contents[self.focus_position][0]
481
-
482
473
  def get_focus(self) -> Widget | None:
483
474
  """
484
475
  Return the widget in focus, for backwards compatibility. You may
@@ -487,8 +478,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
487
478
  """
488
479
  warnings.warn(
489
480
  "for backwards compatibility."
490
- "You may also use the new standard container property .focus to get the child widget in focus.",
491
- PendingDeprecationWarning,
481
+ "You may also use the new standard container property .focus to get the child widget in focus."
482
+ "API will be removed in version 5.0.",
483
+ DeprecationWarning,
492
484
  stacklevel=2,
493
485
  )
494
486
  if not self.contents:
@@ -498,8 +490,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
498
490
  def set_focus(self, item: Widget | int) -> None:
499
491
  warnings.warn(
500
492
  "for backwards compatibility."
501
- "You may also use the new standard container property .focus to get the child widget in focus.",
502
- PendingDeprecationWarning,
493
+ "You may also use the new standard container property .focus to get the child widget in focus."
494
+ "API will be removed in version 5.0.",
495
+ DeprecationWarning,
503
496
  stacklevel=2,
504
497
  )
505
498
  if isinstance(item, int):
@@ -516,7 +509,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
516
509
  warnings.warn(
517
510
  "only for backwards compatibility."
518
511
  "You should use the new standard container properties "
519
- "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
512
+ "`focus` and `focus_position` to get the child widget in focus or modify the focus position."
513
+ "API will be removed in version 4.0.",
520
514
  DeprecationWarning,
521
515
  stacklevel=2,
522
516
  )
@@ -527,7 +521,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
527
521
  warnings.warn(
528
522
  "only for backwards compatibility."
529
523
  "You should use the new standard container properties "
530
- "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
524
+ "`focus` and `focus_position` to get the child widget in focus or modify the focus position."
525
+ "API will be removed in version 4.0.",
531
526
  DeprecationWarning,
532
527
  stacklevel=2,
533
528
  )
@@ -557,36 +552,6 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
557
552
  raise IndexError(f"No Pile child widget at position {position}").with_traceback(exc.__traceback__) from exc
558
553
  self.contents.focus = position
559
554
 
560
- def _get_focus_position(self) -> int | None:
561
- warnings.warn(
562
- f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
563
- f"please use `{self.__class__.__name__}.focus_position` property",
564
- DeprecationWarning,
565
- stacklevel=3,
566
- )
567
- if not self.contents:
568
- raise IndexError("No focus_position, Pile is empty")
569
- return self.contents.focus
570
-
571
- def _set_focus_position(self, position: int) -> None:
572
- """
573
- Set the widget in focus.
574
-
575
- position -- index of child widget to be made focus
576
- """
577
- warnings.warn(
578
- f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
579
- f"please use `{self.__class__.__name__}.focus_position` property",
580
- DeprecationWarning,
581
- stacklevel=3,
582
- )
583
- try:
584
- if position < 0 or position >= len(self.contents):
585
- raise IndexError(f"No Pile child widget at position {position}")
586
- except TypeError as exc:
587
- raise IndexError(f"No Pile child widget at position {position}").with_traceback(exc.__traceback__) from exc
588
- self.contents.focus = position
589
-
590
555
  def get_pref_col(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> int | None:
591
556
  """Return the preferred column for the cursor, or None."""
592
557
  if not self.selectable():
@@ -609,6 +574,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
609
574
  """
610
575
  Return a size appropriate for passing to self.contents[i][0].render
611
576
  """
577
+ warnings.warn(
578
+ "get_item_size is going to be deprecated and can be removed soon."
579
+ "This method is not used by the urwid codebase and `get_rows_sizes` is used for the similar purposes.",
580
+ PendingDeprecationWarning,
581
+ stacklevel=2,
582
+ )
612
583
  _w, (f, height) = self.contents[i]
613
584
  if f == WHSettings.PACK:
614
585
  if not size:
@@ -637,6 +608,10 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
637
608
  self,
638
609
  focus: bool = False,
639
610
  ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int] | tuple[()]]]:
611
+ """Get rows widths, heights and render size parameters
612
+
613
+ Fixed case expect widget sizes calculation with several cycles for unknown height cases.
614
+ """
640
615
  if not self.contents:
641
616
  return (), (), ()
642
617
 
@@ -728,6 +703,55 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
728
703
  tuple(w_h_args[idx] for idx in range(len(w_h_args))),
729
704
  )
730
705
 
706
+ def _get_flow_rows_sizes(
707
+ self,
708
+ size: tuple[int],
709
+ focus: bool = False,
710
+ ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int] | tuple[()]]]:
711
+ """Get rows widths, heights and render size parameters
712
+
713
+ Flow case is the simplest one: minimum cycles in the logic and no widgets manipulation.
714
+ Here we can make some shortcuts
715
+ """
716
+ maxcol = size[0]
717
+ if not self.contents:
718
+ return (maxcol,), (0,), ()
719
+
720
+ widths: Sequence[int] = (maxcol,) * len(self.contents)
721
+ heights: list[int] = []
722
+ w_h_args: list[tuple[int, int] | tuple[int] | tuple[()]] = []
723
+ focus_position = self.focus_position
724
+
725
+ for i, (w, (f, height)) in enumerate(self.contents):
726
+ if isinstance(w, Widget):
727
+ w_sizing = w.sizing()
728
+ else:
729
+ warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
730
+ w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))
731
+
732
+ focused = focus and i == focus_position
733
+
734
+ if f == WHSettings.GIVEN:
735
+ heights.append(height)
736
+ w_h_args.append((maxcol, height))
737
+ elif Sizing.FLOW in w_sizing:
738
+ heights.append(w.rows((maxcol,), focus=focused))
739
+ w_h_args.append((maxcol,))
740
+ elif Sizing.FIXED in w_sizing and f == WHSettings.PACK:
741
+ heights.append(w.pack((), focused)[1])
742
+ w_h_args.append(())
743
+ else:
744
+ warnings.warn(
745
+ f"Unusual widget {i} sizing {w_sizing} for {f.upper()}). "
746
+ f"Assuming wrong sizing and using {Sizing.FLOW.upper()} for height calculation",
747
+ PileWarning,
748
+ stacklevel=3,
749
+ )
750
+ heights.append(w.rows((maxcol,), focus=focused))
751
+ w_h_args.append((maxcol,))
752
+
753
+ return (widths, tuple(heights), tuple(w_h_args))
754
+
731
755
  def get_rows_sizes(
732
756
  self,
733
757
  size: tuple[int, int] | tuple[int] | tuple[()],
@@ -736,13 +760,20 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
736
760
  """Get rows widths, heights and render size parameters"""
737
761
  if not size:
738
762
  return self._get_fixed_rows_sizes(focus=focus)
763
+ if len(size) == 1:
764
+ return self._get_flow_rows_sizes(size, focus=focus)
739
765
 
740
- maxcol = size[0]
741
- item_rows = None
766
+ maxcol, maxrow = size
767
+ if not self.contents:
768
+ return (maxcol,), (maxrow,), ()
742
769
 
743
- widths: list[int] = []
744
- heights: list[int] = []
745
- w_h_args: list[tuple[int, int] | tuple[int] | tuple[()]] = []
770
+ remaining: int = maxrow
771
+
772
+ widths: Sequence[int] = (maxcol,) * len(self.contents)
773
+ heights: dict[int, int] = {}
774
+ weighted: dict[int, int] = {}
775
+ w_h_args: dict[int, tuple[int, int] | tuple[int] | tuple[()]] = {}
776
+ focus_position = self.focus_position
746
777
 
747
778
  for i, (w, (f, height)) in enumerate(self.contents):
748
779
  if isinstance(w, Widget):
@@ -751,12 +782,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
751
782
  warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
752
783
  w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))
753
784
 
754
- item_focus = focus and self.focus == w
755
- widths.append(maxcol)
785
+ focused = focus and i == focus_position
756
786
 
757
787
  if f == WHSettings.GIVEN:
758
- heights.append(height)
759
- w_h_args.append((maxcol, height))
788
+ heights[i] = height
789
+ w_h_args[i] = (maxcol, height)
790
+ remaining -= height
760
791
  elif f == WHSettings.PACK or len(size) == 1:
761
792
  if Sizing.FLOW in w_sizing:
762
793
  w_h_arg: tuple[int] | tuple[()] = (maxcol,)
@@ -771,16 +802,72 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
771
802
  )
772
803
  w_h_arg = (maxcol,)
773
804
 
774
- heights.append(w.pack(w_h_arg, item_focus)[1])
775
- w_h_args.append(w_h_arg)
805
+ item_height = w.pack(w_h_arg, focused)[1]
806
+ heights[i] = item_height
807
+ w_h_args[i] = w_h_arg
808
+ remaining -= item_height
809
+ elif height:
810
+ weighted[i] = height
776
811
  else:
777
- if item_rows is None:
778
- item_rows = self.get_item_rows(size, focus)
779
- rows = item_rows[i]
780
- heights.append(rows)
781
- w_h_args.append((maxcol, rows))
812
+ heights[i] = 0
813
+ w_h_args[i] = (maxcol, 0) # zero-weighted items treated as ('given', 0)
814
+
815
+ sum_weight = sum(weighted.values())
816
+ if remaining <= 0 and focus_position in weighted:
817
+ # We need to hide some widgets: focused part will be not displayed by default
818
+ # At this place we have to operate also negative "remaining" to be sure that widget really fit
819
+ before = []
820
+ after = []
821
+ for idx, height in heights.items():
822
+ if height:
823
+ if idx < focus_position:
824
+ before.append(idx)
825
+ else:
826
+ after.append(idx)
827
+
828
+ # Try to move to the center
829
+ offset = len(before) - len(after)
830
+ if not offset:
831
+ indexes = (element for pair in zip(after[::-1], before) for element in pair)
832
+ elif offset > 0:
833
+ indexes = (
834
+ *before[:offset],
835
+ *(element for pair in zip(after[::-1], before[offset:]) for element in pair),
836
+ )
837
+ else:
838
+ indexes = (
839
+ *after[-1 : offset - 1 : -1],
840
+ *(element for pair in zip(after[offset - 1 :: -1], before) for element in pair),
841
+ )
842
+
843
+ for idx in indexes:
844
+ height, heights[idx] = heights[idx], 0
845
+ remaining += height
846
+ if remaining > 0:
847
+ break
848
+
849
+ remaining = max(remaining, 0)
850
+
851
+ for idx in sorted(weighted, key=lambda idx: (idx != focus_position, idx)):
852
+ # We have to sort weighted items way that focused widget will gain render priority
853
+ if remaining == 0:
854
+ rows = 0
855
+ else:
856
+ rows = int(float(remaining) * weighted[idx] / sum_weight + 0.5)
857
+ if not rows and idx == focus_position:
858
+ # Special case: we have to force as minimum 1 row for focused widget to prevent broken behaviour
859
+ rows = 1
860
+ remaining -= rows
861
+ sum_weight -= weighted[idx]
782
862
 
783
- return (tuple(widths), tuple(heights), tuple(w_h_args))
863
+ heights[idx] = rows
864
+ w_h_args[idx] = (maxcol, rows)
865
+
866
+ return (
867
+ widths,
868
+ tuple(heights[idx] for idx in range(len(heights))),
869
+ tuple(w_h_args[idx] for idx in range(len(w_h_args))),
870
+ )
784
871
 
785
872
  def pack(self, size: tuple[()] | tuple[int] | tuple[int, int] = (), focus: bool = False) -> tuple[int, int]:
786
873
  """Get packed sized for widget."""
@@ -789,74 +876,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
789
876
  widths, heights, _ = self.get_rows_sizes(size, focus)
790
877
  return (max(widths), sum(heights))
791
878
 
792
- def get_item_rows(self, size: tuple[()] | tuple[int] | tuple[int, int], focus: bool) -> list[int]:
793
- """
794
- Return a list of the number of rows used by each widget in self.contents
795
- """
796
- remaining = None
797
- maxcol = size[0]
798
- if len(size) == 2:
799
- remaining = size[1]
800
-
801
- rows_numbers = []
879
+ def get_item_rows(self, size: tuple[int] | tuple[int, int], focus: bool) -> list[int]:
880
+ """A list of the number of rows used by each widget in self.contents.
802
881
 
803
- if remaining is None:
804
- # pile is a flow widget
805
- for i, (w, (f, height)) in enumerate(self.contents):
806
- if isinstance(w, Widget):
807
- w_sizing = w.sizing()
808
- else:
809
- warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
810
- w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))
811
-
812
- focused = focus and self.focus == w
813
-
814
- if f == WHSettings.GIVEN:
815
- rows_numbers.append(height)
816
- elif Sizing.FLOW in w_sizing:
817
- rows_numbers.append(w.rows((maxcol,), focus=focused))
818
- elif Sizing.FIXED in w_sizing and f == WHSettings.PACK:
819
- rows_numbers.append(w.pack((), focused)[0])
820
- else:
821
- warnings.warn(
822
- f"Unusual widget {i} sizing {w_sizing} for {f.upper()}). "
823
- f"Assuming wrong sizing and using {Sizing.FLOW.upper()} for height calculation",
824
- PileWarning,
825
- stacklevel=3,
826
- )
827
- rows_numbers.append(w.rows((maxcol,), focus=focused))
828
- return rows_numbers
829
-
830
- # pile is a box widget
831
- # do an extra pass to calculate rows for each widget
832
- wtotal = 0
833
- for w, (f, height) in self.contents:
834
- if f == WHSettings.PACK:
835
- rows = w.rows((maxcol,), focus=focus and self.focus == w)
836
- rows_numbers.append(rows)
837
- remaining -= rows
838
- elif f == WHSettings.GIVEN:
839
- rows_numbers.append(height)
840
- remaining -= height
841
- elif height:
842
- rows_numbers.append(None)
843
- wtotal += height
844
- else:
845
- rows_numbers.append(0) # zero-weighted items treated as ('given', 0)
846
-
847
- if wtotal == 0:
848
- raise PileError("No weighted widgets found for Pile treated as a box widget")
849
-
850
- remaining = max(remaining, 0)
851
-
852
- for i, (_w, (_f, height)) in enumerate(self.contents):
853
- li = rows_numbers[i]
854
- if li is None:
855
- rows = int(float(remaining) * height / wtotal + 0.5)
856
- rows_numbers[i] = rows
857
- remaining -= rows
858
- wtotal -= height
859
- return rows_numbers
882
+ This method is a normally used only by `get_item_size` for the BOX case..
883
+ """
884
+ return list(self.get_rows_sizes(size, focus)[1])
860
885
 
861
886
  def render(
862
887
  self,
@@ -876,7 +901,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
876
901
  combinelist.append((canv, i, item_focus))
877
902
 
878
903
  if not combinelist:
879
- return SolidCanvas(" ", size[0], (size[1:] + (0,))[0])
904
+ return SolidCanvas(" ", size[0], (*size[1:], 0)[0])
880
905
 
881
906
  out = CanvasCombine(combinelist)
882
907
  if len(size) == 2 and size[1] != out.rows():
@@ -894,18 +919,17 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
894
919
 
895
920
  i = self.focus_position
896
921
  _widths, heights, size_args = self.get_rows_sizes(size, focus=True)
897
- coords = self.focus.get_cursor_coords(size_args[i])
922
+ if (coords := self.focus.get_cursor_coords(size_args[i])) is not None:
923
+ x, y = coords
924
+ if i > 0:
925
+ y += sum(heights[:i])
898
926
 
899
- if coords is None:
900
- return None
901
- x, y = coords
902
- if i > 0:
903
- for r in heights[:i]:
904
- y += r
905
- return x, y
927
+ return x, y
906
928
 
907
- def rows(self, size: tuple[int] | tuple[int, int], focus: bool = False) -> int:
908
- return sum(self.get_item_rows(size, focus))
929
+ return None
930
+
931
+ def rows(self, size: tuple[int], focus: bool = False) -> int:
932
+ return sum(self.get_rows_sizes(size, focus)[1])
909
933
 
910
934
  def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
911
935
  """Pass the keypress to the widget in focus.
@@ -955,8 +979,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
955
979
  if not hasattr(self.focus, "get_pref_col"):
956
980
  return
957
981
 
958
- pref_col = self.focus.get_pref_col(w_size)
959
- if pref_col is not None:
982
+ if (pref_col := self.focus.get_pref_col(w_size)) is not None:
960
983
  self.pref_col = pref_col
961
984
 
962
985
  def move_cursor_to_coords(
@@ -982,10 +1005,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
982
1005
  if not w.selectable():
983
1006
  return False
984
1007
 
985
- if hasattr(w, "move_cursor_to_coords"):
986
- rval = w.move_cursor_to_coords(w_size, col, row - wrow)
987
- if rval is False:
988
- return False
1008
+ if hasattr(w, "move_cursor_to_coords") and w.move_cursor_to_coords(w_size, col, row - wrow) is False:
1009
+ return False
989
1010
 
990
1011
  self.focus_position = i
991
1012
  return True
urwid/widget/popup.py CHANGED
@@ -98,8 +98,8 @@ class PopUpTarget(WidgetDecoration[WrappedWidget]):
98
98
  def _update_overlay(self, size: tuple[int, int], focus: bool) -> None:
99
99
  canv = self._original_widget.render(size, focus=focus)
100
100
  self._cache_original_canvas = canv # imperfect performance hack
101
- pop_up = canv.get_pop_up()
102
- if pop_up:
101
+
102
+ if pop_up := canv.get_pop_up():
103
103
  left, top, (w, overlay_width, overlay_height) = pop_up
104
104
  if self._pop_up != w:
105
105
  self._pop_up = w
@@ -1,13 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import typing
4
- import warnings
5
4
 
6
5
  from .constants import BAR_SYMBOLS, Align, Sizing, WrapMode
7
6
  from .text import Text
8
7
  from .widget import Widget
9
8
 
10
9
  if typing.TYPE_CHECKING:
10
+ from collections.abc import Hashable
11
+
11
12
  from urwid.canvas import TextCanvas
12
13
 
13
14
 
@@ -18,7 +19,14 @@ class ProgressBar(Widget):
18
19
 
19
20
  text_align = Align.CENTER
20
21
 
21
- def __init__(self, normal, complete, current: int = 0, done: int = 100, satt=None):
22
+ def __init__(
23
+ self,
24
+ normal: Hashable | None,
25
+ complete: Hashable | None,
26
+ current: int = 0,
27
+ done: int = 100,
28
+ satt: Hashable | None = None,
29
+ ) -> None:
22
30
  """
23
31
  :param normal: display attribute for incomplete part of progress bar
24
32
  :param complete: display attribute for complete part of progress bar
@@ -86,15 +94,6 @@ class ProgressBar(Widget):
86
94
  self._done = done
87
95
  self._invalidate()
88
96
 
89
- def _set_done(self, done):
90
- warnings.warn(
91
- f"Method `{self.__class__.__name__}._set_done` is deprecated, "
92
- f"please use property `{self.__class__.__name__}.done`",
93
- DeprecationWarning,
94
- stacklevel=2,
95
- )
96
- self.done = done
97
-
98
97
  def rows(self, size: tuple[int], focus: bool = False) -> int:
99
98
  return 1
100
99