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
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from urwid.monitored_list import MonitoredFocusList, MonitoredList
|
|
7
|
+
|
|
8
|
+
from .columns import Columns
|
|
9
|
+
from .constants import Align, Sizing, WHSettings
|
|
10
|
+
from .container import WidgetContainerListContentsMixin, WidgetContainerMixin
|
|
11
|
+
from .divider import Divider
|
|
12
|
+
from .padding import Padding
|
|
13
|
+
from .pile import Pile
|
|
14
|
+
from .widget import Widget, WidgetError, WidgetWrap
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Iterable, Sequence
|
|
18
|
+
|
|
19
|
+
from typing_extensions import Literal
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GridFlowError(WidgetError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GridFlow(WidgetWrap[Pile], WidgetContainerMixin, WidgetContainerListContentsMixin):
|
|
27
|
+
"""
|
|
28
|
+
The GridFlow widget is a flow widget that renders all the widgets it
|
|
29
|
+
contains the same width and it arranges them from left to right and top to
|
|
30
|
+
bottom.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def sizing(self) -> frozenset[Sizing]:
|
|
34
|
+
return frozenset((Sizing.FLOW, Sizing.FIXED))
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
cells: Iterable[Widget],
|
|
39
|
+
cell_width: int,
|
|
40
|
+
h_sep: int,
|
|
41
|
+
v_sep: int,
|
|
42
|
+
align: Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int],
|
|
43
|
+
focus: int | Widget | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
:param cells: iterable of flow widgets to display
|
|
47
|
+
:param cell_width: column width for each cell
|
|
48
|
+
:param h_sep: blank columns between each cell horizontally
|
|
49
|
+
:param v_sep: blank rows between cells vertically
|
|
50
|
+
(if more than one row is required to display all the cells)
|
|
51
|
+
:param align: horizontal alignment of cells, one of:
|
|
52
|
+
'left', 'center', 'right', ('relative', percentage 0=left 100=right)
|
|
53
|
+
:param focus: widget index or widget instance to focus on
|
|
54
|
+
"""
|
|
55
|
+
prepared_contents: list[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]] = []
|
|
56
|
+
focus_position: int = -1
|
|
57
|
+
|
|
58
|
+
for idx, widget in enumerate(cells):
|
|
59
|
+
prepared_contents.append((widget, (WHSettings.GIVEN, cell_width)))
|
|
60
|
+
if focus_position < 0 and (focus in {widget, idx} or (focus is None and widget.selectable())):
|
|
61
|
+
focus_position = idx
|
|
62
|
+
|
|
63
|
+
focus_position = max(focus_position, 0)
|
|
64
|
+
|
|
65
|
+
self._contents: MonitoredFocusList[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]] = MonitoredFocusList(
|
|
66
|
+
prepared_contents, focus=focus_position
|
|
67
|
+
)
|
|
68
|
+
self._contents.set_modified_callback(self._invalidate)
|
|
69
|
+
self._contents.set_focus_changed_callback(lambda f: self._invalidate())
|
|
70
|
+
self._contents.set_validate_contents_modified(self._contents_modified)
|
|
71
|
+
self._cell_width = cell_width
|
|
72
|
+
self.h_sep = h_sep
|
|
73
|
+
self.v_sep = v_sep
|
|
74
|
+
self.align = align
|
|
75
|
+
self._cache_maxcol = self._get_maxcol(())
|
|
76
|
+
super().__init__(self.generate_display_widget((self._cache_maxcol,)))
|
|
77
|
+
|
|
78
|
+
def _invalidate(self) -> None:
|
|
79
|
+
self._cache_maxcol = None
|
|
80
|
+
super()._invalidate()
|
|
81
|
+
|
|
82
|
+
def _contents_modified(
|
|
83
|
+
self, slc, new_items: Iterable[tuple[Widget, tuple[Literal["given", WHSettings.GIVEN], int]]]
|
|
84
|
+
) -> None:
|
|
85
|
+
for item in new_items:
|
|
86
|
+
try:
|
|
87
|
+
_w, (t, _n) = item
|
|
88
|
+
if t != WHSettings.GIVEN:
|
|
89
|
+
raise GridFlowError(f"added content invalid {item!r}")
|
|
90
|
+
except (TypeError, ValueError) as exc: # noqa: PERF203
|
|
91
|
+
raise GridFlowError(f"added content invalid {item!r}").with_traceback(exc.__traceback__) from exc
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def cells(self):
|
|
95
|
+
"""
|
|
96
|
+
A list of the widgets in this GridFlow
|
|
97
|
+
|
|
98
|
+
.. note:: only for backwards compatibility. You should use the new
|
|
99
|
+
standard container property :attr:`contents` to modify GridFlow
|
|
100
|
+
contents.
|
|
101
|
+
"""
|
|
102
|
+
warnings.warn(
|
|
103
|
+
"only for backwards compatibility."
|
|
104
|
+
"You should use the new standard container property `contents` to modify GridFlow",
|
|
105
|
+
PendingDeprecationWarning,
|
|
106
|
+
stacklevel=2,
|
|
107
|
+
)
|
|
108
|
+
ml = MonitoredList(w for w, t in self.contents)
|
|
109
|
+
|
|
110
|
+
def user_modified():
|
|
111
|
+
self.cells = ml
|
|
112
|
+
|
|
113
|
+
ml.set_modified_callback(user_modified)
|
|
114
|
+
return ml
|
|
115
|
+
|
|
116
|
+
@cells.setter
|
|
117
|
+
def cells(self, widgets: Sequence[Widget]):
|
|
118
|
+
warnings.warn(
|
|
119
|
+
"only for backwards compatibility."
|
|
120
|
+
"You should use the new standard container property `contents` to modify GridFlow",
|
|
121
|
+
PendingDeprecationWarning,
|
|
122
|
+
stacklevel=2,
|
|
123
|
+
)
|
|
124
|
+
focus_position = self.focus_position
|
|
125
|
+
self.contents = [(new, (WHSettings.GIVEN, self._cell_width)) for new in widgets]
|
|
126
|
+
if focus_position < len(widgets):
|
|
127
|
+
self.focus_position = focus_position
|
|
128
|
+
|
|
129
|
+
def _get_cells(self):
|
|
130
|
+
warnings.warn(
|
|
131
|
+
"only for backwards compatibility."
|
|
132
|
+
"You should use the new standard container property `contents` to modify GridFlow",
|
|
133
|
+
DeprecationWarning,
|
|
134
|
+
stacklevel=3,
|
|
135
|
+
)
|
|
136
|
+
return self.cells
|
|
137
|
+
|
|
138
|
+
def _set_cells(self, widgets: Sequence[Widget]):
|
|
139
|
+
warnings.warn(
|
|
140
|
+
"only for backwards compatibility."
|
|
141
|
+
"You should use the new standard container property `contents` to modify GridFlow",
|
|
142
|
+
DeprecationWarning,
|
|
143
|
+
stacklevel=3,
|
|
144
|
+
)
|
|
145
|
+
self.cells = widgets
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def cell_width(self) -> int:
|
|
149
|
+
"""
|
|
150
|
+
The width of each cell in the GridFlow. Setting this value affects
|
|
151
|
+
all cells.
|
|
152
|
+
"""
|
|
153
|
+
return self._cell_width
|
|
154
|
+
|
|
155
|
+
@cell_width.setter
|
|
156
|
+
def cell_width(self, width: int) -> None:
|
|
157
|
+
focus_position = self.focus_position
|
|
158
|
+
self.contents = [(w, (WHSettings.GIVEN, width)) for (w, options) in self.contents]
|
|
159
|
+
self.focus_position = focus_position
|
|
160
|
+
self._cell_width = width
|
|
161
|
+
|
|
162
|
+
def _get_cell_width(self) -> int:
|
|
163
|
+
warnings.warn(
|
|
164
|
+
f"Method `{self.__class__.__name__}._get_cell_width` is deprecated, "
|
|
165
|
+
f"please use property `{self.__class__.__name__}.cell_width`",
|
|
166
|
+
DeprecationWarning,
|
|
167
|
+
stacklevel=3,
|
|
168
|
+
)
|
|
169
|
+
return self.cell_width
|
|
170
|
+
|
|
171
|
+
def _set_cell_width(self, width: int) -> None:
|
|
172
|
+
warnings.warn(
|
|
173
|
+
f"Method `{self.__class__.__name__}._set_cell_width` is deprecated, "
|
|
174
|
+
f"please use property `{self.__class__.__name__}.cell_width`",
|
|
175
|
+
DeprecationWarning,
|
|
176
|
+
stacklevel=3,
|
|
177
|
+
)
|
|
178
|
+
self.cell_width = width
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def contents(self) -> MonitoredFocusList[tuple[Widget, tuple[Literal[WHSettings.GIVEN], int]]]:
|
|
182
|
+
"""
|
|
183
|
+
The contents of this GridFlow as a list of (widget, options)
|
|
184
|
+
tuples.
|
|
185
|
+
|
|
186
|
+
options is currently a tuple in the form `('fixed', number)`.
|
|
187
|
+
number is the number of screen columns to allocate to this cell.
|
|
188
|
+
'fixed' is the only type accepted at this time.
|
|
189
|
+
|
|
190
|
+
This list may be modified like a normal list and the GridFlow
|
|
191
|
+
widget will update automatically.
|
|
192
|
+
|
|
193
|
+
.. seealso:: Create new options tuples with the :meth:`options` method.
|
|
194
|
+
"""
|
|
195
|
+
return self._contents
|
|
196
|
+
|
|
197
|
+
@contents.setter
|
|
198
|
+
def contents(self, c):
|
|
199
|
+
self._contents[:] = c
|
|
200
|
+
|
|
201
|
+
def options(
|
|
202
|
+
self,
|
|
203
|
+
width_type: Literal["given", WHSettings.GIVEN] = WHSettings.GIVEN,
|
|
204
|
+
width_amount: int | None = None,
|
|
205
|
+
) -> tuple[Literal[WHSettings.GIVEN], int]:
|
|
206
|
+
"""
|
|
207
|
+
Return a new options tuple for use in a GridFlow's .contents list.
|
|
208
|
+
|
|
209
|
+
width_type -- 'given' is the only value accepted
|
|
210
|
+
width_amount -- None to use the default cell_width for this GridFlow
|
|
211
|
+
"""
|
|
212
|
+
if width_type != WHSettings.GIVEN:
|
|
213
|
+
raise GridFlowError(f"invalid width_type: {width_type!r}")
|
|
214
|
+
if width_amount is None:
|
|
215
|
+
width_amount = self._cell_width
|
|
216
|
+
return (WHSettings(width_type), width_amount)
|
|
217
|
+
|
|
218
|
+
def set_focus(self, cell: Widget | int) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Set the cell in focus, for backwards compatibility.
|
|
221
|
+
|
|
222
|
+
.. note:: only for backwards compatibility. You may also use the new
|
|
223
|
+
standard container property :attr:`focus_position` to get the focus.
|
|
224
|
+
|
|
225
|
+
:param cell: contained element to focus
|
|
226
|
+
:type cell: Widget or int
|
|
227
|
+
"""
|
|
228
|
+
warnings.warn(
|
|
229
|
+
"only for backwards compatibility."
|
|
230
|
+
"You may also use the new standard container property `focus_position` to set the focus.",
|
|
231
|
+
PendingDeprecationWarning,
|
|
232
|
+
stacklevel=2,
|
|
233
|
+
)
|
|
234
|
+
if isinstance(cell, int):
|
|
235
|
+
try:
|
|
236
|
+
if cell < 0 or cell >= len(self.contents):
|
|
237
|
+
raise IndexError(f"No GridFlow child widget at position {cell}")
|
|
238
|
+
except TypeError as exc:
|
|
239
|
+
raise IndexError(f"No GridFlow child widget at position {cell}").with_traceback(
|
|
240
|
+
exc.__traceback__
|
|
241
|
+
) from exc
|
|
242
|
+
self.contents.focus = cell
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
for i, (w, _options) in enumerate(self.contents):
|
|
246
|
+
if cell == w:
|
|
247
|
+
self.focus_position = i
|
|
248
|
+
return
|
|
249
|
+
raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def focus(self) -> Widget | None:
|
|
253
|
+
"""the child widget in focus or None when GridFlow is empty"""
|
|
254
|
+
if not self.contents:
|
|
255
|
+
return None
|
|
256
|
+
return self.contents[self.focus_position][0]
|
|
257
|
+
|
|
258
|
+
def _get_focus(self) -> Widget:
|
|
259
|
+
warnings.warn(
|
|
260
|
+
f"method `{self.__class__.__name__}._get_focus` is deprecated, "
|
|
261
|
+
f"please use `{self.__class__.__name__}.focus` property",
|
|
262
|
+
DeprecationWarning,
|
|
263
|
+
stacklevel=3,
|
|
264
|
+
)
|
|
265
|
+
if not self.contents:
|
|
266
|
+
return None
|
|
267
|
+
return self.contents[self.focus_position][0]
|
|
268
|
+
|
|
269
|
+
def get_focus(self):
|
|
270
|
+
"""
|
|
271
|
+
Return the widget in focus, for backwards compatibility.
|
|
272
|
+
|
|
273
|
+
.. note:: only for backwards compatibility. You may also use the new
|
|
274
|
+
standard container property :attr:`focus` to get the focus.
|
|
275
|
+
"""
|
|
276
|
+
warnings.warn(
|
|
277
|
+
"only for backwards compatibility."
|
|
278
|
+
"You may also use the new standard container property `focus` to get the focus.",
|
|
279
|
+
PendingDeprecationWarning,
|
|
280
|
+
stacklevel=2,
|
|
281
|
+
)
|
|
282
|
+
if not self.contents:
|
|
283
|
+
return None
|
|
284
|
+
return self.contents[self.focus_position][0]
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def focus_cell(self):
|
|
288
|
+
warnings.warn(
|
|
289
|
+
"only for backwards compatibility."
|
|
290
|
+
"You may also use the new standard container property"
|
|
291
|
+
"`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
|
|
292
|
+
PendingDeprecationWarning,
|
|
293
|
+
stacklevel=2,
|
|
294
|
+
)
|
|
295
|
+
return self.focus
|
|
296
|
+
|
|
297
|
+
@focus_cell.setter
|
|
298
|
+
def focus_cell(self, cell: Widget) -> None:
|
|
299
|
+
warnings.warn(
|
|
300
|
+
"only for backwards compatibility."
|
|
301
|
+
"You may also use the new standard container property"
|
|
302
|
+
"`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
|
|
303
|
+
PendingDeprecationWarning,
|
|
304
|
+
stacklevel=2,
|
|
305
|
+
)
|
|
306
|
+
for i, (w, _options) in enumerate(self.contents):
|
|
307
|
+
if cell == w:
|
|
308
|
+
self.focus_position = i
|
|
309
|
+
return
|
|
310
|
+
raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")
|
|
311
|
+
|
|
312
|
+
def _set_focus_cell(self, cell: Widget) -> None:
|
|
313
|
+
warnings.warn(
|
|
314
|
+
"only for backwards compatibility."
|
|
315
|
+
"You may also use the new standard container property"
|
|
316
|
+
"`focus` to get the focus and `focus_position` to get/set the cell in focus by index",
|
|
317
|
+
DeprecationWarning,
|
|
318
|
+
stacklevel=3,
|
|
319
|
+
)
|
|
320
|
+
for i, (w, _options) in enumerate(self.contents):
|
|
321
|
+
if cell == w:
|
|
322
|
+
self.focus_position = i
|
|
323
|
+
return
|
|
324
|
+
raise ValueError(f"Widget not found in GridFlow contents: {cell!r}")
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def focus_position(self) -> int | None:
|
|
328
|
+
"""
|
|
329
|
+
index of child widget in focus.
|
|
330
|
+
Raises :exc:`IndexError` if read when GridFlow is empty, or when set to an invalid index.
|
|
331
|
+
"""
|
|
332
|
+
if not self.contents:
|
|
333
|
+
raise IndexError("No focus_position, GridFlow is empty")
|
|
334
|
+
return self.contents.focus
|
|
335
|
+
|
|
336
|
+
@focus_position.setter
|
|
337
|
+
def focus_position(self, position: int) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Set the widget in focus.
|
|
340
|
+
|
|
341
|
+
position -- index of child widget to be made focus
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
if position < 0 or position >= len(self.contents):
|
|
345
|
+
raise IndexError(f"No GridFlow child widget at position {position}")
|
|
346
|
+
except TypeError as exc:
|
|
347
|
+
raise IndexError(f"No GridFlow child widget at position {position}").with_traceback(
|
|
348
|
+
exc.__traceback__
|
|
349
|
+
) from exc
|
|
350
|
+
self.contents.focus = position
|
|
351
|
+
|
|
352
|
+
def _get_focus_position(self) -> int | None:
|
|
353
|
+
warnings.warn(
|
|
354
|
+
f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
|
|
355
|
+
f"please use `{self.__class__.__name__}.focus_position` property",
|
|
356
|
+
DeprecationWarning,
|
|
357
|
+
stacklevel=3,
|
|
358
|
+
)
|
|
359
|
+
if not self.contents:
|
|
360
|
+
raise IndexError("No focus_position, GridFlow is empty")
|
|
361
|
+
return self.contents.focus
|
|
362
|
+
|
|
363
|
+
def _set_focus_position(self, position: int) -> None:
|
|
364
|
+
"""
|
|
365
|
+
Set the widget in focus.
|
|
366
|
+
|
|
367
|
+
position -- index of child widget to be made focus
|
|
368
|
+
"""
|
|
369
|
+
warnings.warn(
|
|
370
|
+
f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
|
|
371
|
+
f"please use `{self.__class__.__name__}.focus_position` property",
|
|
372
|
+
DeprecationWarning,
|
|
373
|
+
stacklevel=3,
|
|
374
|
+
)
|
|
375
|
+
try:
|
|
376
|
+
if position < 0 or position >= len(self.contents):
|
|
377
|
+
raise IndexError(f"No GridFlow child widget at position {position}")
|
|
378
|
+
except TypeError as exc:
|
|
379
|
+
raise IndexError(f"No GridFlow child widget at position {position}").with_traceback(
|
|
380
|
+
exc.__traceback__
|
|
381
|
+
) from exc
|
|
382
|
+
self.contents.focus = position
|
|
383
|
+
|
|
384
|
+
def _get_maxcol(self, size: tuple[int] | tuple[()]) -> int:
|
|
385
|
+
if size:
|
|
386
|
+
(maxcol,) = size
|
|
387
|
+
else:
|
|
388
|
+
maxcol = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
|
|
389
|
+
return maxcol
|
|
390
|
+
|
|
391
|
+
def get_display_widget(self, size: tuple[int] | tuple[()]) -> Divider | Pile:
|
|
392
|
+
"""
|
|
393
|
+
Arrange the cells into columns (and possibly a pile) for
|
|
394
|
+
display, input or to calculate rows, and update the display
|
|
395
|
+
widget.
|
|
396
|
+
"""
|
|
397
|
+
maxcol = self._get_maxcol(size)
|
|
398
|
+
|
|
399
|
+
# use cache if possible
|
|
400
|
+
if self._cache_maxcol == maxcol:
|
|
401
|
+
return self._w
|
|
402
|
+
|
|
403
|
+
self._cache_maxcol = maxcol
|
|
404
|
+
self._w = self.generate_display_widget((maxcol,))
|
|
405
|
+
|
|
406
|
+
return self._w
|
|
407
|
+
|
|
408
|
+
def generate_display_widget(self, size: tuple[int] | tuple[()]) -> Divider | Pile:
|
|
409
|
+
"""
|
|
410
|
+
Actually generate display widget (ignoring cache)
|
|
411
|
+
"""
|
|
412
|
+
maxcol = self._get_maxcol(size)
|
|
413
|
+
|
|
414
|
+
divider = Divider()
|
|
415
|
+
if not self.contents:
|
|
416
|
+
return divider
|
|
417
|
+
|
|
418
|
+
if self.v_sep > 1:
|
|
419
|
+
# increase size of divider
|
|
420
|
+
divider.top = self.v_sep - 1
|
|
421
|
+
|
|
422
|
+
c = None
|
|
423
|
+
p = Pile([])
|
|
424
|
+
used_space = 0
|
|
425
|
+
|
|
426
|
+
for i, (w, (_width_type, width_amount)) in enumerate(self.contents):
|
|
427
|
+
if c is None or maxcol - used_space < width_amount:
|
|
428
|
+
# starting a new row
|
|
429
|
+
if self.v_sep:
|
|
430
|
+
p.contents.append((divider, p.options()))
|
|
431
|
+
c = Columns([], self.h_sep)
|
|
432
|
+
column_focused = False
|
|
433
|
+
pad = Padding(c, self.align)
|
|
434
|
+
# extra attribute to reference contents position
|
|
435
|
+
pad.first_position = i
|
|
436
|
+
p.contents.append((pad, p.options()))
|
|
437
|
+
|
|
438
|
+
c.contents.append((w, c.options(WHSettings.GIVEN, width_amount)))
|
|
439
|
+
if (i == self.focus_position) or (not column_focused and w.selectable()):
|
|
440
|
+
c.focus_position = len(c.contents) - 1
|
|
441
|
+
column_focused = True
|
|
442
|
+
if i == self.focus_position:
|
|
443
|
+
p.focus_position = len(p.contents) - 1
|
|
444
|
+
used_space = sum(x[1][1] for x in c.contents) + self.h_sep * len(c.contents)
|
|
445
|
+
if width_amount > maxcol:
|
|
446
|
+
# special case: display is too narrow for the given
|
|
447
|
+
# width so we remove the Columns for better behaviour
|
|
448
|
+
# FIXME: determine why this is necessary
|
|
449
|
+
pad.original_widget = w
|
|
450
|
+
pad.width = used_space - self.h_sep
|
|
451
|
+
|
|
452
|
+
if self.v_sep:
|
|
453
|
+
# remove first divider
|
|
454
|
+
del p.contents[:1]
|
|
455
|
+
else:
|
|
456
|
+
# Ensure p __selectable is updated
|
|
457
|
+
p._contents_modified() # pylint: disable=protected-access
|
|
458
|
+
|
|
459
|
+
return p
|
|
460
|
+
|
|
461
|
+
def _set_focus_from_display_widget(self) -> None:
|
|
462
|
+
"""
|
|
463
|
+
Set the focus to the item in focus in the display widget.
|
|
464
|
+
"""
|
|
465
|
+
# display widget (self._w) is always built as:
|
|
466
|
+
#
|
|
467
|
+
# Pile([
|
|
468
|
+
# Padding(
|
|
469
|
+
# Columns([ # possibly
|
|
470
|
+
# cell, ...])),
|
|
471
|
+
# Divider(), # possibly
|
|
472
|
+
# ...])
|
|
473
|
+
|
|
474
|
+
pile_focus = self._w.focus
|
|
475
|
+
if not pile_focus:
|
|
476
|
+
return
|
|
477
|
+
c = pile_focus.base_widget
|
|
478
|
+
if c.focus:
|
|
479
|
+
col_focus_position = c.focus_position
|
|
480
|
+
else:
|
|
481
|
+
col_focus_position = 0
|
|
482
|
+
# pad.first_position was set by generate_display_widget() above
|
|
483
|
+
self.focus_position = pile_focus.first_position + col_focus_position
|
|
484
|
+
|
|
485
|
+
def keypress(self, size: tuple[int], key: str) -> str | None:
|
|
486
|
+
"""
|
|
487
|
+
Pass keypress to display widget for handling.
|
|
488
|
+
Captures focus changes.
|
|
489
|
+
"""
|
|
490
|
+
self.get_display_widget(size)
|
|
491
|
+
key = super().keypress(size, key)
|
|
492
|
+
if key is None:
|
|
493
|
+
self._set_focus_from_display_widget()
|
|
494
|
+
return key
|
|
495
|
+
|
|
496
|
+
def pack(self, size: tuple[int] | tuple[()] = (), focus: bool = False) -> tuple[int, int]:
|
|
497
|
+
if size:
|
|
498
|
+
return super().pack(size, focus)
|
|
499
|
+
cols = len(self) * self.cell_width + (len(self) - 1) * self.h_sep
|
|
500
|
+
return cols, self.rows((cols,), focus)
|
|
501
|
+
|
|
502
|
+
def rows(self, size: tuple[int], focus: bool = False) -> int:
|
|
503
|
+
self.get_display_widget(size)
|
|
504
|
+
return super().rows(size, focus=focus)
|
|
505
|
+
|
|
506
|
+
def render(self, size: tuple[int] | tuple[()], focus: bool = False):
|
|
507
|
+
self.get_display_widget(size)
|
|
508
|
+
return super().render(size, focus)
|
|
509
|
+
|
|
510
|
+
def get_cursor_coords(self, size: tuple[int] | tuple[()]) -> tuple[int, int]:
|
|
511
|
+
"""Get cursor from display widget."""
|
|
512
|
+
self.get_display_widget(size)
|
|
513
|
+
return super().get_cursor_coords(size)
|
|
514
|
+
|
|
515
|
+
def move_cursor_to_coords(self, size: tuple[int] | tuple[()], col: int, row: int):
|
|
516
|
+
"""Set the widget in focus based on the col + row."""
|
|
517
|
+
self.get_display_widget(size)
|
|
518
|
+
rval = super().move_cursor_to_coords(size, col, row)
|
|
519
|
+
self._set_focus_from_display_widget()
|
|
520
|
+
return rval
|
|
521
|
+
|
|
522
|
+
def mouse_event(
|
|
523
|
+
self,
|
|
524
|
+
size: tuple[int] | tuple[()],
|
|
525
|
+
event: str,
|
|
526
|
+
button: int,
|
|
527
|
+
col: int,
|
|
528
|
+
row: int,
|
|
529
|
+
focus: bool,
|
|
530
|
+
) -> Literal[True]:
|
|
531
|
+
self.get_display_widget(size)
|
|
532
|
+
super().mouse_event(size, event, button, col, row, focus)
|
|
533
|
+
self._set_focus_from_display_widget()
|
|
534
|
+
return True # at a minimum we adjusted our focus
|
|
535
|
+
|
|
536
|
+
def get_pref_col(self, size: tuple[int] | tuple[()]):
|
|
537
|
+
"""Return pref col from display widget."""
|
|
538
|
+
self.get_display_widget(size)
|
|
539
|
+
return super().get_pref_col(size)
|