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/frame.py ADDED
@@ -0,0 +1,539 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ import warnings
5
+
6
+ from urwid.canvas import CanvasCombine, CompositeCanvas
7
+ from urwid.util import is_mouse_press
8
+
9
+ from .constants import Sizing
10
+ from .container import WidgetContainerMixin
11
+ from .filler import Filler
12
+ from .widget import Widget, WidgetError
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from collections.abc import Iterator, MutableMapping
16
+
17
+ from typing_extensions import Literal
18
+
19
+
20
+ class FrameError(WidgetError):
21
+ pass
22
+
23
+
24
+ def _check_widget_subclass(widget: Widget | None) -> None:
25
+ if widget is None:
26
+ return
27
+
28
+ if not isinstance(widget, Widget):
29
+ obj_class_path = f"{widget.__class__.__module__}.{widget.__class__.__name__}"
30
+ warnings.warn(
31
+ f"{obj_class_path} is not subclass of Widget",
32
+ DeprecationWarning,
33
+ stacklevel=3,
34
+ )
35
+
36
+
37
+ class Frame(Widget, WidgetContainerMixin):
38
+ """
39
+ Frame widget is a box widget with optional header and footer
40
+ flow widgets placed above and below the box widget.
41
+
42
+ .. note:: The main difference between a Frame and a :class:`Pile` widget
43
+ defined as: `Pile([('pack', header), body, ('pack', footer)])` is that
44
+ the Frame will not automatically change focus up and down in response to
45
+ keystrokes.
46
+ """
47
+
48
+ _selectable = True
49
+ _sizing = frozenset([Sizing.BOX])
50
+
51
+ def __init__(
52
+ self,
53
+ body: Widget,
54
+ header: Widget | None = None,
55
+ footer: Widget | None = None,
56
+ focus_part: Literal["header", "footer", "body"] = "body",
57
+ ):
58
+ """
59
+ :param body: a box widget for the body of the frame
60
+ :type body: Widget
61
+ :param header: a flow widget for above the body (or None)
62
+ :type header: Widget
63
+ :param footer: a flow widget for below the body (or None)
64
+ :type footer: Widget
65
+ :param focus_part: 'header', 'footer' or 'body'
66
+ :type focus_part: str
67
+ """
68
+ super().__init__()
69
+
70
+ self._header = header
71
+ self._body = body
72
+ self._footer = footer
73
+ self.focus_part = focus_part
74
+ _check_widget_subclass(header)
75
+ _check_widget_subclass(body)
76
+ _check_widget_subclass(footer)
77
+
78
+ @property
79
+ def header(self) -> Widget | None:
80
+ return self._header
81
+
82
+ @header.setter
83
+ def header(self, header: Widget | None):
84
+ _check_widget_subclass(header)
85
+ self._header = header
86
+ if header is None and self.focus_part == "header":
87
+ self.focus_part = "body"
88
+ self._invalidate()
89
+
90
+ def get_header(self) -> Widget | None:
91
+ warnings.warn(
92
+ f"method `{self.__class__.__name__}.get_header` is deprecated, "
93
+ f"standard property `{self.__class__.__name__}.header` should be used instead",
94
+ PendingDeprecationWarning,
95
+ stacklevel=2,
96
+ )
97
+ return self.header
98
+
99
+ def set_header(self, header: Widget | None):
100
+ warnings.warn(
101
+ f"method `{self.__class__.__name__}.set_header` is deprecated, "
102
+ f"standard property `{self.__class__.__name__}.header` should be used instead",
103
+ PendingDeprecationWarning,
104
+ stacklevel=2,
105
+ )
106
+ self.header = header
107
+
108
+ @property
109
+ def body(self) -> Widget:
110
+ return self._body
111
+
112
+ @body.setter
113
+ def body(self, body: Widget) -> None:
114
+ _check_widget_subclass(body)
115
+ self._body = body
116
+ self._invalidate()
117
+
118
+ def get_body(self) -> Widget:
119
+ warnings.warn(
120
+ f"method `{self.__class__.__name__}.get_body` is deprecated, "
121
+ f"standard property {self.__class__.__name__}.body should be used instead",
122
+ PendingDeprecationWarning,
123
+ stacklevel=2,
124
+ )
125
+ return self.body
126
+
127
+ def set_body(self, body: Widget) -> None:
128
+ warnings.warn(
129
+ f"method `{self.__class__.__name__}.set_body` is deprecated, "
130
+ f"standard property `{self.__class__.__name__}.body` should be used instead",
131
+ PendingDeprecationWarning,
132
+ stacklevel=2,
133
+ )
134
+ self.body = body
135
+
136
+ @property
137
+ def footer(self) -> Widget | None:
138
+ return self._footer
139
+
140
+ @footer.setter
141
+ def footer(self, footer: Widget | None) -> None:
142
+ _check_widget_subclass(footer)
143
+ self._footer = footer
144
+ if footer is None and self.focus_part == "footer":
145
+ self.focus_part = "body"
146
+ self._invalidate()
147
+
148
+ def get_footer(self) -> Widget | None:
149
+ warnings.warn(
150
+ f"method `{self.__class__.__name__}.get_footer` is deprecated, "
151
+ f"standard property `{self.__class__.__name__}.footer` should be used instead",
152
+ PendingDeprecationWarning,
153
+ stacklevel=2,
154
+ )
155
+ return self.footer
156
+
157
+ def set_footer(self, footer: Widget | None) -> None:
158
+ warnings.warn(
159
+ f"method `{self.__class__.__name__}.set_footer` is deprecated, "
160
+ f"standard property `{self.__class__.__name__}.footer` should be used instead",
161
+ PendingDeprecationWarning,
162
+ stacklevel=2,
163
+ )
164
+ self.footer = footer
165
+
166
+ @property
167
+ def focus_position(self) -> Literal["header", "footer", "body"]:
168
+ """
169
+ writeable property containing an indicator which part of the frame
170
+ that is in focus: `'body', 'header'` or `'footer'`.
171
+
172
+ :returns: one of 'header', 'footer' or 'body'.
173
+ :rtype: str
174
+ """
175
+ return self.focus_part
176
+
177
+ @focus_position.setter
178
+ def focus_position(self, part: Literal["header", "footer", "body"]) -> None:
179
+ """
180
+ Determine which part of the frame is in focus.
181
+
182
+ :param part: 'header', 'footer' or 'body'
183
+ :type part: str
184
+ """
185
+ if part not in {"header", "footer", "body"}:
186
+ raise IndexError(f"Invalid position for Frame: {part}")
187
+ if (part == "header" and self._header is None) or (part == "footer" and self._footer is None):
188
+ raise IndexError(f"This Frame has no {part}")
189
+ self.focus_part = part
190
+ self._invalidate()
191
+
192
+ def get_focus(self) -> Literal["header", "footer", "body"]:
193
+ """
194
+ writeable property containing an indicator which part of the frame
195
+ that is in focus: `'body', 'header'` or `'footer'`.
196
+
197
+ .. note:: included for backwards compatibility. You should rather use
198
+ the container property :attr:`.focus_position` to get this value.
199
+
200
+ :returns: one of 'header', 'footer' or 'body'.
201
+ :rtype: str
202
+ """
203
+ warnings.warn(
204
+ "included for backwards compatibility."
205
+ "You should rather use the container property `.focus_position` to get this value.",
206
+ PendingDeprecationWarning,
207
+ stacklevel=2,
208
+ )
209
+ return self.focus_position
210
+
211
+ def set_focus(self, part: Literal["header", "footer", "body"]) -> None:
212
+ warnings.warn(
213
+ "included for backwards compatibility."
214
+ "You should rather use the container property `.focus_position` to set this value.",
215
+ PendingDeprecationWarning,
216
+ stacklevel=2,
217
+ )
218
+ self.focus_position = part
219
+
220
+ @property
221
+ def focus(self) -> Widget:
222
+ """
223
+ child :class:`Widget` in focus: the body, header or footer widget.
224
+ This is a read-only property."""
225
+ return {"header": self._header, "footer": self._footer, "body": self._body}[self.focus_part]
226
+
227
+ def _get_focus(self) -> Widget:
228
+ warnings.warn(
229
+ f"method `{self.__class__.__name__}._get_focus` is deprecated, "
230
+ f"please use `{self.__class__.__name__}.focus` property",
231
+ DeprecationWarning,
232
+ stacklevel=3,
233
+ )
234
+ return {"header": self._header, "footer": self._footer, "body": self._body}[self.focus_part]
235
+
236
+ @property
237
+ def contents(self) -> MutableMapping[Literal["header", "footer", "body"], tuple[Widget, None]]:
238
+ """
239
+ a dict-like object similar to::
240
+
241
+ {
242
+ 'body': (body_widget, None),
243
+ 'header': (header_widget, None), # if frame has a header
244
+ 'footer': (footer_widget, None) # if frame has a footer
245
+ }
246
+
247
+ This object may be used to read or update the contents of the Frame.
248
+
249
+ The values are similar to the list-like .contents objects used
250
+ in other containers with (:class:`Widget`, options) tuples, but are
251
+ constrained to keys for each of the three usual parts of a Frame.
252
+ When other keys are used a :exc:`KeyError` will be raised.
253
+
254
+ Currently, all options are `None`, but using the :meth:`options` method
255
+ to create the options value is recommended for forwards
256
+ compatibility.
257
+ """
258
+
259
+ # noinspection PyMethodParameters
260
+ class FrameContents(typing.MutableMapping[str, typing.Tuple[Widget, None]]):
261
+ # pylint: disable=no-self-argument
262
+
263
+ def __len__(inner_self) -> int:
264
+ return len(inner_self.keys())
265
+
266
+ __getitem__ = self._contents__getitem__
267
+ __setitem__ = self._contents__setitem__
268
+ __delitem__ = self._contents__delitem__
269
+
270
+ def __iter__(inner_self) -> Iterator[str]:
271
+ yield from inner_self.keys()
272
+
273
+ def __repr__(inner_self) -> str:
274
+ return f"<{inner_self.__class__.__name__}({dict(inner_self)}) for {self}>"
275
+
276
+ def __rich_repr__(inner_self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
277
+ yield from inner_self.items()
278
+
279
+ return FrameContents()
280
+
281
+ def _contents_keys(self) -> list[Literal["header", "footer", "body"]]:
282
+ keys = ["body"]
283
+ if self._header:
284
+ keys.append("header")
285
+ if self._footer:
286
+ keys.append("footer")
287
+ return keys
288
+
289
+ def _contents__getitem__(self, key: Literal["header", "footer", "body"]):
290
+ if key == "body":
291
+ return (self._body, None)
292
+ if key == "header" and self._header:
293
+ return (self._header, None)
294
+ if key == "footer" and self._footer:
295
+ return (self._footer, None)
296
+ raise KeyError(f"Frame.contents has no key: {key!r}")
297
+
298
+ def _contents__setitem__(self, key: Literal["header", "footer", "body"], value):
299
+ if key not in {"body", "header", "footer"}:
300
+ raise KeyError(f"Frame.contents has no key: {key!r}")
301
+ try:
302
+ value_w, value_options = value
303
+ if value_options is not None:
304
+ raise FrameError(f"added content invalid: {value!r}")
305
+ except (ValueError, TypeError) as exc:
306
+ raise FrameError(f"added content invalid: {value!r}").with_traceback(exc.__traceback__) from exc
307
+ if key == "body":
308
+ self.body = value_w
309
+ elif key == "footer":
310
+ self.footer = value_w
311
+ else:
312
+ self.header = value_w
313
+
314
+ def _contents__delitem__(self, key: Literal["header", "footer", "body"]):
315
+ if key not in {"header", "footer"}:
316
+ raise KeyError(f"Frame.contents can't remove key: {key!r}")
317
+ if (key == "header" and self._header is None) or (key == "footer" and self._footer is None):
318
+ raise KeyError(f"Frame.contents has no key: {key!r}")
319
+ if key == "header":
320
+ self.header = None
321
+ else:
322
+ self.footer = None
323
+
324
+ def _contents(self):
325
+ warnings.warn(
326
+ f"method `{self.__class__.__name__}._contents` is deprecated, "
327
+ f"please use property `{self.__class__.__name__}.contents`",
328
+ DeprecationWarning,
329
+ stacklevel=3,
330
+ )
331
+ return self.contents
332
+
333
+ def options(self) -> None:
334
+ """
335
+ There are currently no options for Frame contents.
336
+
337
+ Return None as a placeholder for future options.
338
+ """
339
+
340
+ def frame_top_bottom(self, size: tuple[int, int], focus: bool) -> tuple[tuple[int, int], tuple[int, int]]:
341
+ """
342
+ Calculate the number of rows for the header and footer.
343
+
344
+ :param size: See :meth:`Widget.render` for details
345
+ :type size: widget size
346
+ :param focus: ``True`` if this widget is in focus
347
+ :type focus: bool
348
+ :returns: `(head rows, foot rows),(orig head, orig foot)`
349
+ orig head/foot are from rows() calls.
350
+ :rtype: (int, int), (int, int)
351
+ """
352
+ (maxcol, maxrow) = size
353
+ frows = hrows = 0
354
+
355
+ if self.header:
356
+ hrows = self.header.rows((maxcol,), self.focus_part == "header" and focus)
357
+
358
+ if self.footer:
359
+ frows = self.footer.rows((maxcol,), self.focus_part == "footer" and focus)
360
+
361
+ remaining = maxrow
362
+
363
+ if self.focus_part == "footer":
364
+ if frows >= remaining:
365
+ return (0, remaining), (hrows, frows)
366
+
367
+ remaining -= frows
368
+ if hrows >= remaining:
369
+ return (remaining, frows), (hrows, frows)
370
+
371
+ elif self.focus_part == "header":
372
+ if hrows >= maxrow:
373
+ return (remaining, 0), (hrows, frows)
374
+
375
+ remaining -= hrows
376
+ if frows >= remaining:
377
+ return (hrows, remaining), (hrows, frows)
378
+
379
+ elif hrows + frows >= remaining:
380
+ # self.focus_part == 'body'
381
+ rless1 = max(0, remaining - 1)
382
+ if frows >= remaining - 1:
383
+ return (0, rless1), (hrows, frows)
384
+
385
+ remaining -= frows
386
+ rless1 = max(0, remaining - 1)
387
+ return (rless1, frows), (hrows, frows)
388
+
389
+ return (hrows, frows), (hrows, frows)
390
+
391
+ def render(self, size: tuple[int, int], focus: bool = False) -> CompositeCanvas:
392
+ (maxcol, maxrow) = size
393
+ (htrim, ftrim), (hrows, frows) = self.frame_top_bottom((maxcol, maxrow), focus)
394
+
395
+ combinelist = []
396
+ depends_on = []
397
+
398
+ head = None
399
+ if htrim and htrim < hrows:
400
+ head = Filler(self.header, "top").render((maxcol, htrim), focus and self.focus_part == "header")
401
+ elif htrim:
402
+ head = self.header.render((maxcol,), focus and self.focus_part == "header")
403
+ if head.rows() != hrows:
404
+ raise RuntimeError("rows, render mismatch")
405
+ if head:
406
+ combinelist.append((head, "header", self.focus_part == "header"))
407
+ depends_on.append(self.header)
408
+
409
+ if ftrim + htrim < maxrow:
410
+ body = self.body.render((maxcol, maxrow - ftrim - htrim), focus and self.focus_part == "body")
411
+ combinelist.append((body, "body", self.focus_part == "body"))
412
+ depends_on.append(self.body)
413
+
414
+ foot = None
415
+ if ftrim and ftrim < frows:
416
+ foot = Filler(self.footer, "bottom").render((maxcol, ftrim), focus and self.focus_part == "footer")
417
+ elif ftrim:
418
+ foot = self.footer.render((maxcol,), focus and self.focus_part == "footer")
419
+ if foot.rows() != frows:
420
+ raise RuntimeError("rows, render mismatch")
421
+ if foot:
422
+ combinelist.append((foot, "footer", self.focus_part == "footer"))
423
+ depends_on.append(self.footer)
424
+
425
+ return CanvasCombine(combinelist)
426
+
427
+ def keypress(self, size: tuple[int, int], key: str) -> str | None:
428
+ """Pass keypress to widget in focus."""
429
+ (maxcol, maxrow) = size
430
+
431
+ if self.focus_part == "header" and self.header is not None:
432
+ if not self.header.selectable():
433
+ return key
434
+ return self.header.keypress((maxcol,), key)
435
+ if self.focus_part == "footer" and self.footer is not None:
436
+ if not self.footer.selectable():
437
+ return key
438
+ return self.footer.keypress((maxcol,), key)
439
+ if self.focus_part != "body":
440
+ return key
441
+ remaining = maxrow
442
+ if self.header is not None:
443
+ remaining -= self.header.rows((maxcol,))
444
+ if self.footer is not None:
445
+ remaining -= self.footer.rows((maxcol,))
446
+ if remaining <= 0:
447
+ return key
448
+
449
+ if not self.body.selectable():
450
+ return key
451
+ return self.body.keypress((maxcol, remaining), key)
452
+
453
+ def mouse_event(
454
+ self,
455
+ size: tuple[int, int],
456
+ event: str,
457
+ button: int,
458
+ col: int,
459
+ row: int,
460
+ focus: bool,
461
+ ) -> bool | None:
462
+ """
463
+ Pass mouse event to appropriate part of frame.
464
+ Focus may be changed on button 1 press.
465
+ """
466
+ (maxcol, maxrow) = size
467
+ (htrim, ftrim), (_hrows, _frows) = self.frame_top_bottom((maxcol, maxrow), focus)
468
+
469
+ if row < htrim: # within header
470
+ focus = focus and self.focus_part == "header"
471
+ if is_mouse_press(event) and button == 1 and self.header.selectable():
472
+ self.focus_position = "header"
473
+ if not hasattr(self.header, "mouse_event"):
474
+ return False
475
+ return self.header.mouse_event((maxcol,), event, button, col, row, focus)
476
+
477
+ if row >= maxrow - ftrim: # within footer
478
+ focus = focus and self.focus_part == "footer"
479
+ if is_mouse_press(event) and button == 1 and self.footer.selectable():
480
+ self.focus_position = "footer"
481
+ if not hasattr(self.footer, "mouse_event"):
482
+ return False
483
+ return self.footer.mouse_event((maxcol,), event, button, col, row - maxrow + ftrim, focus)
484
+
485
+ # within body
486
+ focus = focus and self.focus_part == "body"
487
+ if is_mouse_press(event) and button == 1 and self.body.selectable():
488
+ self.focus_position = "body"
489
+
490
+ if not hasattr(self.body, "mouse_event"):
491
+ return False
492
+ return self.body.mouse_event((maxcol, maxrow - htrim - ftrim), event, button, col, row - htrim, focus)
493
+
494
+ def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:
495
+ """Return the cursor coordinates of the focus widget."""
496
+ if not self.focus.selectable():
497
+ return None
498
+ if not hasattr(self.focus, "get_cursor_coords"):
499
+ return None
500
+
501
+ fp = self.focus_position
502
+ (maxcol, maxrow) = size
503
+ (hrows, frows), _ = self.frame_top_bottom(size, True)
504
+
505
+ if fp == "header":
506
+ row_adjust = 0
507
+ coords = self.header.get_cursor_coords((maxcol,))
508
+ elif fp == "body":
509
+ row_adjust = hrows
510
+ coords = self.body.get_cursor_coords((maxcol, maxrow - hrows - frows))
511
+ else:
512
+ row_adjust = maxrow - frows
513
+ coords = self.footer.get_cursor_coords((maxcol,))
514
+
515
+ if coords is None:
516
+ return None
517
+
518
+ x, y = coords
519
+ return x, y + row_adjust
520
+
521
+ def __iter__(self):
522
+ """
523
+ Return an iterator over the positions in this Frame top to bottom.
524
+ """
525
+ if self._header:
526
+ yield "header"
527
+ yield "body"
528
+ if self._footer:
529
+ yield "footer"
530
+
531
+ def __reversed__(self):
532
+ """
533
+ Return an iterator over the positions in this Frame bottom to top.
534
+ """
535
+ if self._footer:
536
+ yield "footer"
537
+ yield "body"
538
+ if self._header:
539
+ yield "header"