urwid 2.6.0.post0__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 (75) hide show
  1. urwid/__init__.py +333 -0
  2. urwid/canvas.py +1413 -0
  3. urwid/command_map.py +137 -0
  4. urwid/container.py +59 -0
  5. urwid/decoration.py +65 -0
  6. urwid/display/__init__.py +97 -0
  7. urwid/display/_posix_raw_display.py +413 -0
  8. urwid/display/_raw_display_base.py +914 -0
  9. urwid/display/_web.css +12 -0
  10. urwid/display/_web.js +462 -0
  11. urwid/display/_win32.py +171 -0
  12. urwid/display/_win32_raw_display.py +269 -0
  13. urwid/display/common.py +1219 -0
  14. urwid/display/curses.py +690 -0
  15. urwid/display/escape.py +624 -0
  16. urwid/display/html_fragment.py +251 -0
  17. urwid/display/lcd.py +518 -0
  18. urwid/display/raw.py +37 -0
  19. urwid/display/web.py +636 -0
  20. urwid/event_loop/__init__.py +55 -0
  21. urwid/event_loop/abstract_loop.py +175 -0
  22. urwid/event_loop/asyncio_loop.py +231 -0
  23. urwid/event_loop/glib_loop.py +294 -0
  24. urwid/event_loop/main_loop.py +721 -0
  25. urwid/event_loop/select_loop.py +230 -0
  26. urwid/event_loop/tornado_loop.py +206 -0
  27. urwid/event_loop/trio_loop.py +302 -0
  28. urwid/event_loop/twisted_loop.py +269 -0
  29. urwid/event_loop/zmq_loop.py +275 -0
  30. urwid/font.py +695 -0
  31. urwid/graphics.py +96 -0
  32. urwid/highlight.css +19 -0
  33. urwid/listbox.py +1899 -0
  34. urwid/monitored_list.py +522 -0
  35. urwid/numedit.py +376 -0
  36. urwid/signals.py +330 -0
  37. urwid/split_repr.py +130 -0
  38. urwid/str_util.py +358 -0
  39. urwid/text_layout.py +632 -0
  40. urwid/treetools.py +515 -0
  41. urwid/util.py +557 -0
  42. urwid/version.py +16 -0
  43. urwid/vterm.py +1806 -0
  44. urwid/widget/__init__.py +181 -0
  45. urwid/widget/attr_map.py +161 -0
  46. urwid/widget/attr_wrap.py +140 -0
  47. urwid/widget/bar_graph.py +649 -0
  48. urwid/widget/big_text.py +77 -0
  49. urwid/widget/box_adapter.py +126 -0
  50. urwid/widget/columns.py +1145 -0
  51. urwid/widget/constants.py +574 -0
  52. urwid/widget/container.py +227 -0
  53. urwid/widget/divider.py +110 -0
  54. urwid/widget/edit.py +718 -0
  55. urwid/widget/filler.py +403 -0
  56. urwid/widget/frame.py +539 -0
  57. urwid/widget/grid_flow.py +539 -0
  58. urwid/widget/line_box.py +194 -0
  59. urwid/widget/overlay.py +829 -0
  60. urwid/widget/padding.py +597 -0
  61. urwid/widget/pile.py +971 -0
  62. urwid/widget/popup.py +170 -0
  63. urwid/widget/progress_bar.py +141 -0
  64. urwid/widget/scrollable.py +597 -0
  65. urwid/widget/solid_fill.py +44 -0
  66. urwid/widget/text.py +354 -0
  67. urwid/widget/widget.py +852 -0
  68. urwid/widget/widget_decoration.py +166 -0
  69. urwid/widget/wimp.py +792 -0
  70. urwid/wimp.py +23 -0
  71. urwid-2.6.0.post0.dist-info/COPYING +504 -0
  72. urwid-2.6.0.post0.dist-info/METADATA +332 -0
  73. urwid-2.6.0.post0.dist-info/RECORD +75 -0
  74. urwid-2.6.0.post0.dist-info/WHEEL +5 -0
  75. urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
urwid/widget/filler.py ADDED
@@ -0,0 +1,403 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ import warnings
5
+
6
+ from urwid.canvas import CompositeCanvas
7
+ from urwid.split_repr import remove_defaults
8
+ from urwid.util import int_scale
9
+
10
+ from .constants import (
11
+ RELATIVE_100,
12
+ Sizing,
13
+ VAlign,
14
+ WHSettings,
15
+ normalize_height,
16
+ normalize_valign,
17
+ simplify_height,
18
+ simplify_valign,
19
+ )
20
+ from .widget_decoration import WidgetDecoration, WidgetError
21
+
22
+ if typing.TYPE_CHECKING:
23
+ from typing_extensions import Literal
24
+
25
+
26
+ WrappedWidget = typing.TypeVar("WrappedWidget")
27
+
28
+
29
+ class FillerError(WidgetError):
30
+ pass
31
+
32
+
33
+ class Filler(WidgetDecoration[WrappedWidget]):
34
+ def __init__(
35
+ self,
36
+ body: WrappedWidget,
37
+ valign: (
38
+ Literal["top", "middle", "bottom"] | VAlign | tuple[Literal["relative", WHSettings.RELATIVE], int]
39
+ ) = VAlign.MIDDLE,
40
+ height: (
41
+ int | Literal["pack", WHSettings.PACK] | tuple[Literal["relative", WHSettings.RELATIVE], int] | None
42
+ ) = WHSettings.PACK,
43
+ min_height: int | None = None,
44
+ top: int = 0,
45
+ bottom: int = 0,
46
+ ) -> None:
47
+ """
48
+ :param body: a flow widget or box widget to be filled around (stored as self.original_widget)
49
+ :type body: Widget
50
+
51
+ :param valign: one of:
52
+ ``'top'``, ``'middle'``, ``'bottom'``,
53
+ (``'relative'``, *percentage* 0=top 100=bottom)
54
+
55
+ :param height: one of:
56
+
57
+ ``'pack'``
58
+ if body is a flow widget
59
+
60
+ *given height*
61
+ integer number of rows for self.original_widget
62
+
63
+ (``'relative'``, *percentage of total height*)
64
+ make height depend on container's height
65
+
66
+ :param min_height: one of:
67
+
68
+ ``None``
69
+ if no minimum or if body is a flow widget
70
+
71
+ *minimum height*
72
+ integer number of rows for the widget when height not fixed
73
+
74
+ :param top: a fixed number of rows to fill at the top
75
+ :type top: int
76
+ :param bottom: a fixed number of rows to fill at the bottom
77
+ :type bottom: int
78
+
79
+ If body is a flow widget, then height must be ``'pack'`` and *min_height* will be ignored.
80
+ Sizing of the filler will be BOX/FLOW in this case.
81
+
82
+ If height is integer, *min_height* will be ignored and sizing of filler will be BOX/FLOW.
83
+
84
+ Filler widgets will try to satisfy height argument first by reducing the valign amount when necessary.
85
+ If height still cannot be satisfied it will also be reduced.
86
+ """
87
+ super().__init__(body)
88
+
89
+ # convert old parameters to the new top/bottom values
90
+ if isinstance(height, tuple):
91
+ if height[0] == "fixed top":
92
+ if not isinstance(valign, tuple) or valign[0] != "fixed bottom":
93
+ raise FillerError("fixed top height may only be used with fixed bottom valign")
94
+ top = height[1]
95
+ height = RELATIVE_100
96
+ elif height[0] == "fixed bottom":
97
+ if not isinstance(valign, tuple) or valign[0] != "fixed top":
98
+ raise FillerError("fixed bottom height may only be used with fixed top valign")
99
+ bottom = height[1]
100
+ height = RELATIVE_100
101
+
102
+ if isinstance(valign, tuple):
103
+ if valign[0] == "fixed top":
104
+ top = valign[1]
105
+ normalized_valign = VAlign.TOP
106
+ elif valign[0] == "fixed bottom":
107
+ bottom = valign[1]
108
+ normalized_valign = VAlign.BOTTOM
109
+ else:
110
+ normalized_valign = valign
111
+
112
+ elif not isinstance(valign, (VAlign, str)):
113
+ raise FillerError(f"invalid valign: {valign!r}")
114
+
115
+ else:
116
+ normalized_valign = VAlign(valign)
117
+
118
+ # convert old flow mode parameter height=None to height='flow'
119
+ if height is None or height == Sizing.FLOW:
120
+ height = WHSettings.PACK
121
+
122
+ self.top = top
123
+ self.bottom = bottom
124
+ self.valign_type, self.valign_amount = normalize_valign(normalized_valign, FillerError)
125
+ self.height_type, self.height_amount = normalize_height(height, FillerError)
126
+
127
+ if self.height_type not in {WHSettings.GIVEN, WHSettings.PACK}:
128
+ self.min_height = min_height
129
+ else:
130
+ self.min_height = None
131
+
132
+ def sizing(self) -> frozenset[Sizing]:
133
+ """Widget sizing.
134
+
135
+ Sizing BOX is always supported.
136
+ Sizing FLOW is supported if: FLOW widget (a height type is PACK) or BOX widget with height GIVEN
137
+ """
138
+ sizing: set[Sizing] = {Sizing.BOX}
139
+ if self.height_type in {WHSettings.PACK, WHSettings.GIVEN}:
140
+ sizing.add(Sizing.FLOW)
141
+ return frozenset(sizing)
142
+
143
+ def rows(self, size: tuple[int], focus: bool = False) -> int:
144
+ """Flow pack support if FLOW sizing supported."""
145
+ if self.height_type == WHSettings.PACK:
146
+ return self.original_widget.rows(size, focus) + self.top + self.bottom
147
+ if self.height_type == WHSettings.GIVEN:
148
+ return self.height_amount + self.top + self.bottom
149
+ raise FillerError("Method 'rows' not supported for BOX widgets") # pragma: no cover
150
+
151
+ def _repr_attrs(self) -> dict[str, typing.Any]:
152
+ attrs = {
153
+ **super()._repr_attrs(),
154
+ "valign": simplify_valign(self.valign_type, self.valign_amount),
155
+ "height": simplify_height(self.height_type, self.height_amount),
156
+ "top": self.top,
157
+ "bottom": self.bottom,
158
+ "min_height": self.min_height,
159
+ }
160
+ return remove_defaults(attrs, Filler.__init__)
161
+
162
+ @property
163
+ def body(self) -> WrappedWidget:
164
+ """backwards compatibility, widget used to be stored as body"""
165
+ warnings.warn(
166
+ "backwards compatibility, widget used to be stored as body",
167
+ PendingDeprecationWarning,
168
+ stacklevel=2,
169
+ )
170
+ return self.original_widget
171
+
172
+ @body.setter
173
+ def body(self, new_body: WrappedWidget) -> None:
174
+ warnings.warn(
175
+ "backwards compatibility, widget used to be stored as body",
176
+ PendingDeprecationWarning,
177
+ stacklevel=2,
178
+ )
179
+ self.original_widget = new_body
180
+
181
+ def get_body(self) -> WrappedWidget:
182
+ """backwards compatibility, widget used to be stored as body"""
183
+ warnings.warn(
184
+ "backwards compatibility, widget used to be stored as body",
185
+ DeprecationWarning,
186
+ stacklevel=2,
187
+ )
188
+ return self.original_widget
189
+
190
+ def set_body(self, new_body: WrappedWidget) -> None:
191
+ warnings.warn(
192
+ "backwards compatibility, widget used to be stored as body",
193
+ DeprecationWarning,
194
+ stacklevel=2,
195
+ )
196
+ self.original_widget = new_body
197
+
198
+ def selectable(self) -> bool:
199
+ """Return selectable from body."""
200
+ return self._original_widget.selectable()
201
+
202
+ def filler_values(self, size: tuple[int, int] | tuple[int], focus: bool) -> tuple[int, int]:
203
+ """
204
+ Return the number of rows to pad on the top and bottom.
205
+
206
+ Override this method to define custom padding behaviour.
207
+ """
208
+ maxcol, maxrow = self.pack(size, focus)
209
+
210
+ if self.height_type == WHSettings.PACK:
211
+ height = self._original_widget.rows((maxcol,), focus=focus)
212
+ return calculate_top_bottom_filler(
213
+ maxrow,
214
+ self.valign_type,
215
+ self.valign_amount,
216
+ WHSettings.GIVEN,
217
+ height,
218
+ None,
219
+ self.top,
220
+ self.bottom,
221
+ )
222
+
223
+ return calculate_top_bottom_filler(
224
+ maxrow,
225
+ self.valign_type,
226
+ self.valign_amount,
227
+ self.height_type,
228
+ self.height_amount,
229
+ self.min_height,
230
+ self.top,
231
+ self.bottom,
232
+ )
233
+
234
+ def render(self, size: tuple[int, int] | tuple[int], focus: bool = False) -> CompositeCanvas:
235
+ """Render self.original_widget with space above and/or below."""
236
+ maxcol, maxrow = self.pack(size, focus)
237
+ top, bottom = self.filler_values(size, focus)
238
+
239
+ if self.height_type == WHSettings.PACK:
240
+ canv = self._original_widget.render((maxcol,), focus)
241
+ else:
242
+ canv = self._original_widget.render((maxcol, maxrow - top - bottom), focus)
243
+ canv = CompositeCanvas(canv)
244
+
245
+ if maxrow and canv.rows() > maxrow and canv.cursor is not None:
246
+ _cx, cy = canv.cursor
247
+ if cy >= maxrow:
248
+ canv.trim(cy - maxrow + 1, maxrow - top - bottom)
249
+ if canv.rows() > maxrow:
250
+ canv.trim(0, maxrow)
251
+ return canv
252
+ canv.pad_trim_top_bottom(top, bottom)
253
+ return canv
254
+
255
+ def keypress(self, size: tuple[int, int] | tuple[()], key: str) -> str | None:
256
+ """Pass keypress to self.original_widget."""
257
+ maxcol, maxrow = self.pack(size, True)
258
+ if self.height_type == WHSettings.PACK:
259
+ return self._original_widget.keypress((maxcol,), key)
260
+
261
+ top, bottom = self.filler_values((maxcol, maxrow), True)
262
+ return self._original_widget.keypress((maxcol, maxrow - top - bottom), key)
263
+
264
+ def get_cursor_coords(self, size: tuple[int, int] | tuple[int]) -> tuple[int, int] | None:
265
+ """Return cursor coords from self.original_widget if any."""
266
+ maxcol, maxrow = self.pack(size, True)
267
+ if not hasattr(self._original_widget, "get_cursor_coords"):
268
+ return None
269
+
270
+ top, bottom = self.filler_values(size, True)
271
+ if self.height_type == WHSettings.PACK:
272
+ coords = self._original_widget.get_cursor_coords((maxcol,))
273
+ else:
274
+ coords = self._original_widget.get_cursor_coords((maxcol, maxrow - top - bottom))
275
+ if not coords:
276
+ return None
277
+ x, y = coords
278
+ if y >= maxrow:
279
+ y = maxrow - 1
280
+ return x, y + top
281
+
282
+ def get_pref_col(self, size: tuple[int, int] | tuple[int]) -> int | None:
283
+ """Return pref_col from self.original_widget if any."""
284
+ maxcol, maxrow = self.pack(size, True)
285
+ if not hasattr(self._original_widget, "get_pref_col"):
286
+ return None
287
+
288
+ if self.height_type == WHSettings.PACK:
289
+ x = self._original_widget.get_pref_col((maxcol,))
290
+ else:
291
+ top, bottom = self.filler_values(size, True)
292
+ x = self._original_widget.get_pref_col((maxcol, maxrow - top - bottom))
293
+
294
+ return x
295
+
296
+ def move_cursor_to_coords(self, size: tuple[int, int] | tuple[int], col: int, row: int) -> bool:
297
+ """Pass to self.original_widget."""
298
+ maxcol, maxrow = self.pack(size, True)
299
+ if not hasattr(self._original_widget, "move_cursor_to_coords"):
300
+ return True
301
+
302
+ top, bottom = self.filler_values(size, True)
303
+ if row < top or row >= maxcol - bottom:
304
+ return False
305
+
306
+ if self.height_type == WHSettings.PACK:
307
+ return self._original_widget.move_cursor_to_coords((maxcol,), col, row - top)
308
+ return self._original_widget.move_cursor_to_coords((maxcol, maxrow - top - bottom), col, row - top)
309
+
310
+ def mouse_event(
311
+ self,
312
+ size: tuple[int, int] | tuple[int],
313
+ event,
314
+ button: int,
315
+ col: int,
316
+ row: int,
317
+ focus: bool,
318
+ ) -> bool | None:
319
+ """Pass to self.original_widget."""
320
+ maxcol, maxrow = self.pack(size, focus)
321
+ if not hasattr(self._original_widget, "mouse_event"):
322
+ return False
323
+
324
+ top, bottom = self.filler_values(size, True)
325
+ if row < top or row >= maxrow - bottom:
326
+ return False
327
+
328
+ if self.height_type == WHSettings.PACK:
329
+ return self._original_widget.mouse_event((maxcol,), event, button, col, row - top, focus)
330
+ return self._original_widget.mouse_event((maxcol, maxrow - top - bottom), event, button, col, row - top, focus)
331
+
332
+
333
+ def calculate_top_bottom_filler(
334
+ maxrow: int,
335
+ valign_type: Literal["top", "middle", "bottom", "relative", WHSettings.RELATIVE] | VAlign,
336
+ valign_amount: int,
337
+ height_type: Literal["given", "relative", "clip", WHSettings.GIVEN, WHSettings.RELATIVE, WHSettings.CLIP],
338
+ height_amount: int,
339
+ min_height: int | None,
340
+ top: int,
341
+ bottom: int,
342
+ ) -> tuple[int, int]:
343
+ """
344
+ Return the amount of filler (or clipping) on the top and
345
+ bottom part of maxrow rows to satisfy the following:
346
+
347
+ valign_type -- 'top', 'middle', 'bottom', 'relative'
348
+ valign_amount -- a percentage when align_type=='relative'
349
+ height_type -- 'given', 'relative', 'clip'
350
+ height_amount -- a percentage when width_type=='relative'
351
+ otherwise equal to the height of the widget
352
+ min_height -- a desired minimum width for the widget or None
353
+ top -- a fixed number of rows to fill on the top
354
+ bottom -- a fixed number of rows to fill on the bottom
355
+
356
+ >>> ctbf = calculate_top_bottom_filler
357
+ >>> ctbf(15, 'top', 0, 'given', 10, None, 2, 0)
358
+ (2, 3)
359
+ >>> ctbf(15, 'relative', 0, 'given', 10, None, 2, 0)
360
+ (2, 3)
361
+ >>> ctbf(15, 'relative', 100, 'given', 10, None, 2, 0)
362
+ (5, 0)
363
+ >>> ctbf(15, 'middle', 0, 'given', 4, None, 2, 0)
364
+ (6, 5)
365
+ >>> ctbf(15, 'middle', 0, 'given', 18, None, 2, 0)
366
+ (0, 0)
367
+ >>> ctbf(20, 'top', 0, 'relative', 60, None, 0, 0)
368
+ (0, 8)
369
+ >>> ctbf(20, 'relative', 30, 'relative', 60, None, 0, 0)
370
+ (2, 6)
371
+ >>> ctbf(20, 'relative', 30, 'relative', 60, 14, 0, 0)
372
+ (2, 4)
373
+ """
374
+ if height_type == WHSettings.RELATIVE:
375
+ maxheight = max(maxrow - top - bottom, 0)
376
+ height = int_scale(height_amount, 101, maxheight + 1)
377
+ if min_height is not None:
378
+ height = max(height, min_height)
379
+ else:
380
+ height = height_amount
381
+
382
+ valign = {VAlign.TOP: 0, VAlign.MIDDLE: 50, VAlign.BOTTOM: 100}.get(valign_type, valign_amount)
383
+
384
+ # add the remainder of top/bottom to the filler
385
+ filler = maxrow - height - top - bottom
386
+ bottom += int_scale(100 - valign, 101, filler + 1)
387
+ top = maxrow - height - bottom
388
+
389
+ # reduce filler if we are clipping an edge
390
+ if bottom < 0 < top:
391
+ shift = min(top, -bottom)
392
+ top -= shift
393
+ bottom += shift
394
+ elif top < 0 < bottom:
395
+ shift = min(bottom, -top)
396
+ bottom -= shift
397
+ top += shift
398
+
399
+ # no negative values for filler at the moment
400
+ top = max(top, 0)
401
+ bottom = max(bottom, 0)
402
+
403
+ return top, bottom