urwid 2.6.16__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.
- urwid/__init__.py +1 -4
- urwid/canvas.py +18 -37
- urwid/command_map.py +4 -3
- urwid/container.py +1 -1
- urwid/decoration.py +1 -1
- urwid/display/_raw_display_base.py +8 -5
- urwid/display/_win32_raw_display.py +11 -13
- urwid/display/common.py +26 -55
- urwid/display/curses.py +1 -1
- urwid/display/escape.py +6 -8
- urwid/display/lcd.py +4 -6
- urwid/display/web.py +7 -12
- urwid/event_loop/asyncio_loop.py +1 -2
- urwid/event_loop/main_loop.py +13 -18
- urwid/event_loop/tornado_loop.py +4 -5
- urwid/event_loop/trio_loop.py +1 -1
- urwid/font.py +10 -15
- urwid/signals.py +2 -1
- urwid/str_util.py +15 -18
- urwid/text_layout.py +6 -7
- urwid/util.py +6 -17
- urwid/version.py +9 -4
- urwid/vterm.py +9 -44
- urwid/widget/__init__.py +0 -6
- urwid/widget/attr_wrap.py +8 -10
- urwid/widget/bar_graph.py +2 -7
- urwid/widget/big_text.py +9 -7
- urwid/widget/box_adapter.py +4 -4
- urwid/widget/columns.py +50 -81
- urwid/widget/container.py +29 -75
- urwid/widget/edit.py +8 -8
- urwid/widget/filler.py +6 -6
- urwid/widget/frame.py +28 -37
- urwid/widget/grid_flow.py +24 -109
- urwid/widget/line_box.py +13 -0
- urwid/widget/listbox.py +12 -50
- urwid/widget/monitored_list.py +6 -4
- urwid/widget/overlay.py +4 -37
- urwid/widget/padding.py +11 -48
- urwid/widget/pile.py +177 -156
- urwid/widget/popup.py +2 -2
- urwid/widget/progress_bar.py +0 -10
- urwid/widget/scrollable.py +24 -32
- urwid/widget/treetools.py +27 -48
- urwid/widget/widget.py +7 -124
- urwid/widget/widget_decoration.py +4 -33
- urwid/wimp.py +1 -1
- {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info}/METADATA +9 -11
- urwid-3.0.0.dist-info/RECORD +74 -0
- {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info}/WHEEL +1 -1
- urwid-2.6.16.dist-info/RECORD +0 -74
- {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info/licenses}/COPYING +0 -0
- {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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)
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
741
|
-
|
|
766
|
+
maxcol, maxrow = size
|
|
767
|
+
if not self.contents:
|
|
768
|
+
return (maxcol,), (maxrow,), ()
|
|
742
769
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
-
|
|
755
|
-
widths.append(maxcol)
|
|
785
|
+
focused = focus and i == focus_position
|
|
756
786
|
|
|
757
787
|
if f == WHSettings.GIVEN:
|
|
758
|
-
heights
|
|
759
|
-
w_h_args
|
|
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
|
-
|
|
775
|
-
|
|
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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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:]
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
908
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
urwid/widget/progress_bar.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
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
|
|
@@ -95,15 +94,6 @@ class ProgressBar(Widget):
|
|
|
95
94
|
self._done = done
|
|
96
95
|
self._invalidate()
|
|
97
96
|
|
|
98
|
-
def _set_done(self, done):
|
|
99
|
-
warnings.warn(
|
|
100
|
-
f"Method `{self.__class__.__name__}._set_done` is deprecated, "
|
|
101
|
-
f"please use property `{self.__class__.__name__}.done`",
|
|
102
|
-
DeprecationWarning,
|
|
103
|
-
stacklevel=2,
|
|
104
|
-
)
|
|
105
|
-
self.done = done
|
|
106
|
-
|
|
107
97
|
def rows(self, size: tuple[int], focus: bool = False) -> int:
|
|
108
98
|
return 1
|
|
109
99
|
|
urwid/widget/scrollable.py
CHANGED
|
@@ -25,8 +25,6 @@ import contextlib
|
|
|
25
25
|
import enum
|
|
26
26
|
import typing
|
|
27
27
|
|
|
28
|
-
from typing_extensions import Protocol, runtime_checkable
|
|
29
|
-
|
|
30
28
|
from .constants import BOX_SYMBOLS, SHADE_SYMBOLS, Sizing
|
|
31
29
|
from .widget_decoration import WidgetDecoration, WidgetError
|
|
32
30
|
|
|
@@ -84,8 +82,8 @@ class ScrollbarSymbols(str, enum.Enum):
|
|
|
84
82
|
DRAWING_DOUBLE = BOX_SYMBOLS.DOUBLE.VERTICAL
|
|
85
83
|
|
|
86
84
|
|
|
87
|
-
@runtime_checkable
|
|
88
|
-
class WidgetProto(Protocol):
|
|
85
|
+
@typing.runtime_checkable
|
|
86
|
+
class WidgetProto(typing.Protocol):
|
|
89
87
|
"""Protocol for widget.
|
|
90
88
|
|
|
91
89
|
Due to protocol cannot inherit non-protocol bases, define several obligatory Widget methods.
|
|
@@ -117,8 +115,8 @@ class WidgetProto(Protocol):
|
|
|
117
115
|
def render(self, size: tuple[int, int], focus: bool = False) -> Canvas: ...
|
|
118
116
|
|
|
119
117
|
|
|
120
|
-
@runtime_checkable
|
|
121
|
-
class SupportsScroll(WidgetProto, Protocol):
|
|
118
|
+
@typing.runtime_checkable
|
|
119
|
+
class SupportsScroll(WidgetProto, typing.Protocol):
|
|
122
120
|
"""Scroll specific methods."""
|
|
123
121
|
|
|
124
122
|
def get_scrollpos(self, size: tuple[int, int], focus: bool = False) -> int: ...
|
|
@@ -126,8 +124,8 @@ class SupportsScroll(WidgetProto, Protocol):
|
|
|
126
124
|
def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int: ...
|
|
127
125
|
|
|
128
126
|
|
|
129
|
-
@runtime_checkable
|
|
130
|
-
class SupportsRelativeScroll(WidgetProto, Protocol):
|
|
127
|
+
@typing.runtime_checkable
|
|
128
|
+
class SupportsRelativeScroll(WidgetProto, typing.Protocol):
|
|
131
129
|
"""Relative scroll-specific methods."""
|
|
132
130
|
|
|
133
131
|
def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool: ...
|
|
@@ -140,10 +138,7 @@ class SupportsRelativeScroll(WidgetProto, Protocol):
|
|
|
140
138
|
def orig_iter(w: Widget) -> Iterator[Widget]:
|
|
141
139
|
visited = {w}
|
|
142
140
|
yield w
|
|
143
|
-
while
|
|
144
|
-
w = w.original_widget
|
|
145
|
-
if w in visited:
|
|
146
|
-
break
|
|
141
|
+
while (w := getattr(w, "original_widget", w)) not in visited:
|
|
147
142
|
visited.add(w)
|
|
148
143
|
yield w
|
|
149
144
|
|
|
@@ -194,8 +189,8 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
|
|
|
194
189
|
first_visible = False
|
|
195
190
|
for pwi, (w, _o) in enumerate(ow.contents):
|
|
196
191
|
wcanv = w.render((maxcol,))
|
|
197
|
-
|
|
198
|
-
if wh:
|
|
192
|
+
|
|
193
|
+
if wh := wcanv.rows():
|
|
199
194
|
ch += wh
|
|
200
195
|
|
|
201
196
|
if not last_hidden and ch >= self._trim_top:
|
|
@@ -235,17 +230,13 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
|
|
|
235
230
|
canv = canvas.CompositeCanvas(canv_full)
|
|
236
231
|
canv_cols, canv_rows = canv.cols(), canv.rows()
|
|
237
232
|
|
|
238
|
-
if canv_cols <= maxcol:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# Canvas is narrower than available horizontal space
|
|
242
|
-
canv.pad_trim_left_right(0, pad_width)
|
|
233
|
+
if canv_cols <= maxcol and (pad_width := maxcol - canv_cols) > 0:
|
|
234
|
+
# Canvas is narrower than available horizontal space
|
|
235
|
+
canv.pad_trim_left_right(0, pad_width)
|
|
243
236
|
|
|
244
|
-
if canv_rows <= maxrow:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
# Canvas is lower than available vertical space
|
|
248
|
-
canv.pad_trim_top_bottom(0, fill_height)
|
|
237
|
+
if canv_rows <= maxrow and (fill_height := maxrow - canv_rows) > 0:
|
|
238
|
+
# Canvas is lower than available vertical space
|
|
239
|
+
canv.pad_trim_top_bottom(0, fill_height)
|
|
249
240
|
|
|
250
241
|
if canv_cols <= maxcol and canv_rows <= maxrow:
|
|
251
242
|
# Canvas is small enough to fit without trimming
|
|
@@ -552,17 +543,18 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
|
|
|
552
543
|
use_relative = False
|
|
553
544
|
|
|
554
545
|
if not use_relative:
|
|
555
|
-
|
|
556
|
-
|
|
546
|
+
if ow_base.rows_max(size, focus) > maxrow:
|
|
547
|
+
ow_canv = render_for_scrollbar()
|
|
548
|
+
# re-calculate using wrapped size
|
|
549
|
+
ow_rows_max = ow_base.rows_max(ow_size, focus)
|
|
550
|
+
pos = ow_base.get_scrollpos(ow_size, focus)
|
|
551
|
+
posmax = ow_rows_max - maxrow
|
|
552
|
+
thumb_weight = min(1.0, maxrow / max(1, ow_rows_max))
|
|
553
|
+
|
|
554
|
+
else:
|
|
557
555
|
# Canvas fits without scrolling - no scrollbar needed
|
|
558
556
|
return render_no_scrollbar()
|
|
559
557
|
|
|
560
|
-
ow_canv = render_for_scrollbar()
|
|
561
|
-
ow_rows_max = ow_base.rows_max(ow_size, focus)
|
|
562
|
-
pos = ow_base.get_scrollpos(ow_size, focus)
|
|
563
|
-
posmax = ow_rows_max - maxrow
|
|
564
|
-
thumb_weight = min(1.0, maxrow / max(1, ow_rows_max))
|
|
565
|
-
|
|
566
558
|
# Thumb shrinks/grows according to the ratio of <number of visible lines> / <number of total lines>
|
|
567
559
|
thumb_height = max(1, round(thumb_weight * maxrow)) # pylint: disable=possibly-used-before-assignment
|
|
568
560
|
|