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.
- urwid/__init__.py +333 -0
- urwid/canvas.py +1413 -0
- urwid/command_map.py +137 -0
- urwid/container.py +59 -0
- urwid/decoration.py +65 -0
- urwid/display/__init__.py +97 -0
- urwid/display/_posix_raw_display.py +413 -0
- urwid/display/_raw_display_base.py +914 -0
- urwid/display/_web.css +12 -0
- urwid/display/_web.js +462 -0
- urwid/display/_win32.py +171 -0
- urwid/display/_win32_raw_display.py +269 -0
- urwid/display/common.py +1219 -0
- urwid/display/curses.py +690 -0
- urwid/display/escape.py +624 -0
- urwid/display/html_fragment.py +251 -0
- urwid/display/lcd.py +518 -0
- urwid/display/raw.py +37 -0
- urwid/display/web.py +636 -0
- urwid/event_loop/__init__.py +55 -0
- urwid/event_loop/abstract_loop.py +175 -0
- urwid/event_loop/asyncio_loop.py +231 -0
- urwid/event_loop/glib_loop.py +294 -0
- urwid/event_loop/main_loop.py +721 -0
- urwid/event_loop/select_loop.py +230 -0
- urwid/event_loop/tornado_loop.py +206 -0
- urwid/event_loop/trio_loop.py +302 -0
- urwid/event_loop/twisted_loop.py +269 -0
- urwid/event_loop/zmq_loop.py +275 -0
- urwid/font.py +695 -0
- urwid/graphics.py +96 -0
- urwid/highlight.css +19 -0
- urwid/listbox.py +1899 -0
- urwid/monitored_list.py +522 -0
- urwid/numedit.py +376 -0
- urwid/signals.py +330 -0
- urwid/split_repr.py +130 -0
- urwid/str_util.py +358 -0
- urwid/text_layout.py +632 -0
- urwid/treetools.py +515 -0
- urwid/util.py +557 -0
- urwid/version.py +16 -0
- urwid/vterm.py +1806 -0
- urwid/widget/__init__.py +181 -0
- urwid/widget/attr_map.py +161 -0
- urwid/widget/attr_wrap.py +140 -0
- urwid/widget/bar_graph.py +649 -0
- urwid/widget/big_text.py +77 -0
- urwid/widget/box_adapter.py +126 -0
- urwid/widget/columns.py +1145 -0
- urwid/widget/constants.py +574 -0
- urwid/widget/container.py +227 -0
- urwid/widget/divider.py +110 -0
- urwid/widget/edit.py +718 -0
- urwid/widget/filler.py +403 -0
- urwid/widget/frame.py +539 -0
- urwid/widget/grid_flow.py +539 -0
- urwid/widget/line_box.py +194 -0
- urwid/widget/overlay.py +829 -0
- urwid/widget/padding.py +597 -0
- urwid/widget/pile.py +971 -0
- urwid/widget/popup.py +170 -0
- urwid/widget/progress_bar.py +141 -0
- urwid/widget/scrollable.py +597 -0
- urwid/widget/solid_fill.py +44 -0
- urwid/widget/text.py +354 -0
- urwid/widget/widget.py +852 -0
- urwid/widget/widget_decoration.py +166 -0
- urwid/widget/wimp.py +792 -0
- urwid/wimp.py +23 -0
- urwid-2.6.0.post0.dist-info/COPYING +504 -0
- urwid-2.6.0.post0.dist-info/METADATA +332 -0
- urwid-2.6.0.post0.dist-info/RECORD +75 -0
- urwid-2.6.0.post0.dist-info/WHEEL +5 -0
- 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"
|