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/padding.py
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from urwid.canvas import CompositeCanvas, SolidCanvas
|
|
7
|
+
from urwid.split_repr import remove_defaults
|
|
8
|
+
from urwid.util import int_scale
|
|
9
|
+
|
|
10
|
+
from .constants import (
|
|
11
|
+
RELATIVE_100,
|
|
12
|
+
Align,
|
|
13
|
+
Sizing,
|
|
14
|
+
WHSettings,
|
|
15
|
+
normalize_align,
|
|
16
|
+
normalize_width,
|
|
17
|
+
simplify_align,
|
|
18
|
+
simplify_width,
|
|
19
|
+
)
|
|
20
|
+
from .widget_decoration import WidgetDecoration, WidgetError, WidgetWarning
|
|
21
|
+
|
|
22
|
+
if typing.TYPE_CHECKING:
|
|
23
|
+
from typing_extensions import Literal
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
WrappedWidget = typing.TypeVar("WrappedWidget")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PaddingError(WidgetError):
|
|
30
|
+
"""Padding related errors."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PaddingWarning(WidgetWarning):
|
|
34
|
+
"""Padding related warnings."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Padding(WidgetDecoration[WrappedWidget]):
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
w: WrappedWidget,
|
|
41
|
+
align: (
|
|
42
|
+
Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int]
|
|
43
|
+
) = Align.LEFT,
|
|
44
|
+
width: (
|
|
45
|
+
int
|
|
46
|
+
| Literal["pack", "clip", WHSettings.PACK, WHSettings.CLIP]
|
|
47
|
+
| tuple[Literal["relative", WHSettings.RELATIVE], int]
|
|
48
|
+
) = RELATIVE_100,
|
|
49
|
+
min_width: int | None = None,
|
|
50
|
+
left: int = 0,
|
|
51
|
+
right: int = 0,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
:param w: a box, flow or fixed widget to pad on the left and/or right
|
|
55
|
+
this widget is stored as self.original_widget
|
|
56
|
+
:type w: Widget
|
|
57
|
+
|
|
58
|
+
:param align: one of: ``'left'``, ``'center'``, ``'right'``
|
|
59
|
+
(``'relative'``, *percentage* 0=left 100=right)
|
|
60
|
+
|
|
61
|
+
:param width: one of:
|
|
62
|
+
|
|
63
|
+
*given width*
|
|
64
|
+
integer number of columns for self.original_widget
|
|
65
|
+
|
|
66
|
+
``'pack'``
|
|
67
|
+
try to pack self.original_widget to its ideal size
|
|
68
|
+
|
|
69
|
+
(``'relative'``, *percentage of total width*)
|
|
70
|
+
make width depend on the container's width
|
|
71
|
+
|
|
72
|
+
``'clip'``
|
|
73
|
+
to enable clipping mode for a fixed widget
|
|
74
|
+
|
|
75
|
+
:param min_width: the minimum number of columns for
|
|
76
|
+
self.original_widget or ``None``
|
|
77
|
+
:type min_width: int | None
|
|
78
|
+
|
|
79
|
+
:param left: a fixed number of columns to pad on the left
|
|
80
|
+
:type left: int
|
|
81
|
+
|
|
82
|
+
:param right: a fixed number of columns to pad on the right
|
|
83
|
+
:type right: int
|
|
84
|
+
|
|
85
|
+
Clipping Mode: (width= ``'clip'``)
|
|
86
|
+
In clipping mode this padding widget will behave as a flow
|
|
87
|
+
widget and self.original_widget will be treated as a fixed widget.
|
|
88
|
+
self.original_widget will be clipped to fit the available number of columns.
|
|
89
|
+
For example if align is ``'left'`` then self.original_widget may be clipped on the right.
|
|
90
|
+
|
|
91
|
+
Pack Mode: (width= ``'pack'``)
|
|
92
|
+
In pack mode is supported FIXED operation if it is supported by the original widget.
|
|
93
|
+
|
|
94
|
+
>>> from urwid import Divider, Text, BigText, FontRegistry
|
|
95
|
+
>>> from urwid.util import set_temporary_encoding
|
|
96
|
+
>>> size = (7,)
|
|
97
|
+
>>> def pr(w):
|
|
98
|
+
... with set_temporary_encoding("utf-8"):
|
|
99
|
+
... for t in w.render(size).text:
|
|
100
|
+
... print(f"|{t.decode('utf-8')}|" )
|
|
101
|
+
>>> pr(Padding(Text(u"Head"), ('relative', 20), 'pack'))
|
|
102
|
+
| Head |
|
|
103
|
+
>>> pr(Padding(Divider(u"-"), left=2, right=1))
|
|
104
|
+
| ---- |
|
|
105
|
+
>>> pr(Padding(Divider(u"*"), 'center', 3))
|
|
106
|
+
| *** |
|
|
107
|
+
>>> p=Padding(Text(u"1234"), 'left', 2, None, 1, 1)
|
|
108
|
+
>>> p
|
|
109
|
+
<Padding fixed/flow widget <Text fixed/flow widget '1234'> left=1 right=1 width=2>
|
|
110
|
+
>>> pr(p) # align against left
|
|
111
|
+
| 12 |
|
|
112
|
+
| 34 |
|
|
113
|
+
>>> p.align = 'right'
|
|
114
|
+
>>> pr(p) # align against right
|
|
115
|
+
| 12 |
|
|
116
|
+
| 34 |
|
|
117
|
+
>>> pr(Padding(Text(u"hi\\nthere"), 'right', 'pack')) # pack text first
|
|
118
|
+
| hi |
|
|
119
|
+
| there|
|
|
120
|
+
>>> pr(Padding(BigText("1,2,3", FontRegistry['Thin 3x3']()), width="clip"))
|
|
121
|
+
| ┐ ┌─┐|
|
|
122
|
+
| │ ┌─┘|
|
|
123
|
+
| ┴ ,└─ |
|
|
124
|
+
"""
|
|
125
|
+
super().__init__(w)
|
|
126
|
+
|
|
127
|
+
# convert obsolete parameters 'fixed left' and 'fixed right':
|
|
128
|
+
if isinstance(align, tuple) and align[0] in {"fixed left", "fixed right"}:
|
|
129
|
+
if align[0] == "fixed left":
|
|
130
|
+
left = align[1]
|
|
131
|
+
align = Align.LEFT
|
|
132
|
+
else:
|
|
133
|
+
right = align[1]
|
|
134
|
+
align = Align.RIGHT
|
|
135
|
+
if isinstance(width, tuple) and width[0] in {"fixed left", "fixed right"}:
|
|
136
|
+
if width[0] == "fixed left":
|
|
137
|
+
left = width[1]
|
|
138
|
+
else:
|
|
139
|
+
right = width[1]
|
|
140
|
+
width = RELATIVE_100
|
|
141
|
+
|
|
142
|
+
# convert old clipping mode width=None to width='clip'
|
|
143
|
+
if width is None:
|
|
144
|
+
width = WHSettings.CLIP
|
|
145
|
+
|
|
146
|
+
self.left = left
|
|
147
|
+
self.right = right
|
|
148
|
+
self._align_type, self._align_amount = normalize_align(align, PaddingError)
|
|
149
|
+
self._width_type, self._width_amount = normalize_width(width, PaddingError)
|
|
150
|
+
self.min_width = min_width
|
|
151
|
+
|
|
152
|
+
def sizing(self) -> frozenset[Sizing]:
|
|
153
|
+
"""Widget sizing.
|
|
154
|
+
|
|
155
|
+
Rules:
|
|
156
|
+
* width == CLIP: only FLOW is supported, and wrapped widget should support FIXED
|
|
157
|
+
* width == GIVEN: FIXED is supported, and wrapped widget should support FLOW
|
|
158
|
+
* All other cases: use sizing of target widget
|
|
159
|
+
"""
|
|
160
|
+
if self._width_type == WHSettings.CLIP:
|
|
161
|
+
return frozenset((Sizing.FLOW,))
|
|
162
|
+
|
|
163
|
+
sizing = set(self.original_widget.sizing())
|
|
164
|
+
if self._width_type == WHSettings.GIVEN:
|
|
165
|
+
if Sizing.FLOW in sizing:
|
|
166
|
+
sizing.add(Sizing.FIXED)
|
|
167
|
+
|
|
168
|
+
elif Sizing.BOX not in sizing:
|
|
169
|
+
warnings.warn(
|
|
170
|
+
f"WHSettings.GIVEN expect BOX or FLOW widget to be used, but received {self.original_widget}",
|
|
171
|
+
PaddingWarning,
|
|
172
|
+
stacklevel=3,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return frozenset(sizing)
|
|
176
|
+
|
|
177
|
+
def _repr_attrs(self) -> dict[str, typing.Any]:
|
|
178
|
+
attrs = {
|
|
179
|
+
**super()._repr_attrs(),
|
|
180
|
+
"align": self.align,
|
|
181
|
+
"width": self.width,
|
|
182
|
+
"left": self.left,
|
|
183
|
+
"right": self.right,
|
|
184
|
+
"min_width": self.min_width,
|
|
185
|
+
}
|
|
186
|
+
return remove_defaults(attrs, Padding.__init__)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def align(
|
|
190
|
+
self,
|
|
191
|
+
) -> Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int]:
|
|
192
|
+
"""
|
|
193
|
+
Return the padding alignment setting.
|
|
194
|
+
"""
|
|
195
|
+
return simplify_align(self._align_type, self._align_amount)
|
|
196
|
+
|
|
197
|
+
@align.setter
|
|
198
|
+
def align(
|
|
199
|
+
self, align: Literal["left", "center", "right"] | Align | tuple[Literal["relative", WHSettings.RELATIVE], int]
|
|
200
|
+
) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Set the padding alignment.
|
|
203
|
+
"""
|
|
204
|
+
self._align_type, self._align_amount = normalize_align(align, PaddingError)
|
|
205
|
+
self._invalidate()
|
|
206
|
+
|
|
207
|
+
def _get_align(self) -> Literal["left", "center", "right"] | tuple[Literal["relative"], int]:
|
|
208
|
+
warnings.warn(
|
|
209
|
+
f"Method `{self.__class__.__name__}._get_align` is deprecated, "
|
|
210
|
+
f"please use property `{self.__class__.__name__}.align`",
|
|
211
|
+
DeprecationWarning,
|
|
212
|
+
stacklevel=2,
|
|
213
|
+
)
|
|
214
|
+
return self.align
|
|
215
|
+
|
|
216
|
+
def _set_align(self, align: Literal["left", "center", "right"] | tuple[Literal["relative"], int]) -> None:
|
|
217
|
+
warnings.warn(
|
|
218
|
+
f"Method `{self.__class__.__name__}._set_align` is deprecated, "
|
|
219
|
+
f"please use property `{self.__class__.__name__}.align`",
|
|
220
|
+
DeprecationWarning,
|
|
221
|
+
stacklevel=2,
|
|
222
|
+
)
|
|
223
|
+
self.align = align
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def width(
|
|
227
|
+
self,
|
|
228
|
+
) -> (
|
|
229
|
+
Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK]
|
|
230
|
+
| int
|
|
231
|
+
| tuple[Literal["relative", WHSettings.RELATIVE], int]
|
|
232
|
+
):
|
|
233
|
+
"""
|
|
234
|
+
Return the padding width.
|
|
235
|
+
"""
|
|
236
|
+
return simplify_width(self._width_type, self._width_amount)
|
|
237
|
+
|
|
238
|
+
@width.setter
|
|
239
|
+
def width(
|
|
240
|
+
self,
|
|
241
|
+
width: (
|
|
242
|
+
Literal["clip", "pack", WHSettings.CLIP, WHSettings.PACK]
|
|
243
|
+
| int
|
|
244
|
+
| tuple[Literal["relative", WHSettings.RELATIVE], int]
|
|
245
|
+
),
|
|
246
|
+
) -> None:
|
|
247
|
+
"""
|
|
248
|
+
Set the padding width.
|
|
249
|
+
"""
|
|
250
|
+
self._width_type, self._width_amount = normalize_width(width, PaddingError)
|
|
251
|
+
self._invalidate()
|
|
252
|
+
|
|
253
|
+
def _get_width(self) -> Literal["clip", "pack"] | int | tuple[Literal["relative"], int]:
|
|
254
|
+
warnings.warn(
|
|
255
|
+
f"Method `{self.__class__.__name__}._get_width` is deprecated, "
|
|
256
|
+
f"please use property `{self.__class__.__name__}.width`",
|
|
257
|
+
DeprecationWarning,
|
|
258
|
+
stacklevel=2,
|
|
259
|
+
)
|
|
260
|
+
return self.width
|
|
261
|
+
|
|
262
|
+
def _set_width(self, width: Literal["clip", "pack"] | int | tuple[Literal["relative"], int]) -> None:
|
|
263
|
+
warnings.warn(
|
|
264
|
+
f"Method `{self.__class__.__name__}._set_width` is deprecated, "
|
|
265
|
+
f"please use property `{self.__class__.__name__}.width`",
|
|
266
|
+
DeprecationWarning,
|
|
267
|
+
stacklevel=2,
|
|
268
|
+
)
|
|
269
|
+
self.width = width
|
|
270
|
+
|
|
271
|
+
def pack(self, size: tuple[()] | tuple[int] | tuple[int, int] = (), focus: bool = False) -> tuple[int, int]:
|
|
272
|
+
if size:
|
|
273
|
+
return super().pack(size, focus)
|
|
274
|
+
if self._width_type == WHSettings.CLIP:
|
|
275
|
+
raise PaddingError("WHSettings.CLIP makes Padding FLOW-only widget")
|
|
276
|
+
|
|
277
|
+
expand = self.left + self.right
|
|
278
|
+
w_sizing = self.original_widget.sizing()
|
|
279
|
+
|
|
280
|
+
if self._width_type == WHSettings.GIVEN:
|
|
281
|
+
if Sizing.FLOW not in w_sizing:
|
|
282
|
+
warnings.warn(
|
|
283
|
+
f"WHSettings.GIVEN expect FLOW widget to be used for FIXED pack/render, "
|
|
284
|
+
f"but received {self.original_widget}",
|
|
285
|
+
PaddingWarning,
|
|
286
|
+
stacklevel=3,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
max(self._width_amount, self.min_width or 1) + expand,
|
|
291
|
+
self.original_widget.rows((self._width_amount,), focus),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if Sizing.FIXED not in w_sizing:
|
|
295
|
+
warnings.warn(
|
|
296
|
+
f"Padded widget should support FIXED sizing for FIXED render, but received {self.original_widget}",
|
|
297
|
+
PaddingWarning,
|
|
298
|
+
stacklevel=3,
|
|
299
|
+
)
|
|
300
|
+
width, height = self.original_widget.pack(size, focus)
|
|
301
|
+
|
|
302
|
+
if self._width_type == WHSettings.PACK:
|
|
303
|
+
return max(width, self.min_width or 1) + expand, height
|
|
304
|
+
|
|
305
|
+
if self._width_type == WHSettings.RELATIVE:
|
|
306
|
+
return max(int(width * 100 / self._width_amount + 0.5), self.min_width or 1) + expand, height
|
|
307
|
+
|
|
308
|
+
raise PaddingError(f"Unexpected width type: {self._width_type.upper()})")
|
|
309
|
+
|
|
310
|
+
def render(
|
|
311
|
+
self,
|
|
312
|
+
size: tuple[()] | tuple[int] | tuple[int, int],
|
|
313
|
+
focus: bool = False,
|
|
314
|
+
) -> CompositeCanvas:
|
|
315
|
+
left, right = self.padding_values(size, focus)
|
|
316
|
+
|
|
317
|
+
if self._width_type == WHSettings.CLIP:
|
|
318
|
+
canv = self._original_widget.render((), focus)
|
|
319
|
+
elif size:
|
|
320
|
+
maxcol = size[0] - (left + right)
|
|
321
|
+
if self._width_type == WHSettings.GIVEN and maxcol < self._width_amount:
|
|
322
|
+
warnings.warn(
|
|
323
|
+
f"{self}.render(size={size}, focus={focus}): too narrow size ({maxcol!r} < {self._width_amount!r})",
|
|
324
|
+
PaddingWarning,
|
|
325
|
+
stacklevel=3,
|
|
326
|
+
)
|
|
327
|
+
canv = self._original_widget.render((maxcol,) + size[1:], focus)
|
|
328
|
+
elif self._width_type == WHSettings.GIVEN:
|
|
329
|
+
canv = self._original_widget.render((self._width_amount,) + size[1:], focus)
|
|
330
|
+
else:
|
|
331
|
+
canv = self._original_widget.render((), focus)
|
|
332
|
+
|
|
333
|
+
if canv.cols() == 0:
|
|
334
|
+
canv = SolidCanvas(" ", size[0], canv.rows())
|
|
335
|
+
canv = CompositeCanvas(canv)
|
|
336
|
+
canv.set_depends([self._original_widget])
|
|
337
|
+
return canv
|
|
338
|
+
|
|
339
|
+
canv = CompositeCanvas(canv)
|
|
340
|
+
canv.set_depends([self._original_widget])
|
|
341
|
+
if left != 0 or right != 0:
|
|
342
|
+
canv.pad_trim_left_right(left, right)
|
|
343
|
+
|
|
344
|
+
return canv
|
|
345
|
+
|
|
346
|
+
def padding_values(
|
|
347
|
+
self,
|
|
348
|
+
size: tuple[()] | tuple[int] | tuple[int, int],
|
|
349
|
+
focus: bool,
|
|
350
|
+
) -> tuple[int, int]:
|
|
351
|
+
"""Return the number of columns to pad on the left and right.
|
|
352
|
+
|
|
353
|
+
Override this method to define custom padding behaviour."""
|
|
354
|
+
if self._width_type == WHSettings.CLIP:
|
|
355
|
+
width, _ignore = self._original_widget.pack((), focus=focus)
|
|
356
|
+
if not size:
|
|
357
|
+
raise PaddingError("WHSettings.CLIP makes Padding FLOW-only widget")
|
|
358
|
+
return calculate_left_right_padding(
|
|
359
|
+
size[0],
|
|
360
|
+
self._align_type,
|
|
361
|
+
self._align_amount,
|
|
362
|
+
WHSettings.CLIP,
|
|
363
|
+
width,
|
|
364
|
+
None,
|
|
365
|
+
self.left,
|
|
366
|
+
self.right,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if self._width_type == WHSettings.PACK:
|
|
370
|
+
if size:
|
|
371
|
+
maxcol = size[0]
|
|
372
|
+
maxwidth = max(maxcol - self.left - self.right, self.min_width or 0)
|
|
373
|
+
(width, _ignore) = self._original_widget.pack((maxwidth,), focus=focus)
|
|
374
|
+
else:
|
|
375
|
+
(width, _ignore) = self._original_widget.pack((), focus=focus)
|
|
376
|
+
maxcol = width + self.left + self.right
|
|
377
|
+
|
|
378
|
+
return calculate_left_right_padding(
|
|
379
|
+
maxcol,
|
|
380
|
+
self._align_type,
|
|
381
|
+
self._align_amount,
|
|
382
|
+
WHSettings.GIVEN,
|
|
383
|
+
width,
|
|
384
|
+
self.min_width,
|
|
385
|
+
self.left,
|
|
386
|
+
self.right,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if size:
|
|
390
|
+
maxcol = size[0]
|
|
391
|
+
elif self._width_type == WHSettings.GIVEN:
|
|
392
|
+
maxcol = self._width_amount + self.left + self.right
|
|
393
|
+
else:
|
|
394
|
+
maxcol = (
|
|
395
|
+
max(self._original_widget.pack((), focus=focus)[0] * 100 // self._width_amount, self.min_width or 1)
|
|
396
|
+
+ self.left
|
|
397
|
+
+ self.right
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
return calculate_left_right_padding(
|
|
401
|
+
maxcol,
|
|
402
|
+
self._align_type,
|
|
403
|
+
self._align_amount,
|
|
404
|
+
self._width_type,
|
|
405
|
+
self._width_amount,
|
|
406
|
+
self.min_width,
|
|
407
|
+
self.left,
|
|
408
|
+
self.right,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def rows(self, size: tuple[int], focus: bool = False) -> int:
|
|
412
|
+
"""Return the rows needed for self.original_widget."""
|
|
413
|
+
(maxcol,) = size
|
|
414
|
+
left, right = self.padding_values(size, focus)
|
|
415
|
+
if self._width_type == WHSettings.PACK:
|
|
416
|
+
_pcols, prows = self._original_widget.pack((maxcol - left - right,), focus)
|
|
417
|
+
return prows
|
|
418
|
+
if self._width_type == WHSettings.CLIP:
|
|
419
|
+
_fcols, frows = self._original_widget.pack((), focus)
|
|
420
|
+
return frows
|
|
421
|
+
return self._original_widget.rows((maxcol - left - right,), focus=focus)
|
|
422
|
+
|
|
423
|
+
def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
|
|
424
|
+
"""Pass keypress to self._original_widget."""
|
|
425
|
+
left, right = self.padding_values(size, True)
|
|
426
|
+
if size:
|
|
427
|
+
maxvals = (size[0] - left - right,) + size[1:]
|
|
428
|
+
return self._original_widget.keypress(maxvals, key)
|
|
429
|
+
return self._original_widget.keypress((), key)
|
|
430
|
+
|
|
431
|
+
def get_cursor_coords(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> tuple[int, int] | None:
|
|
432
|
+
"""Return the (x,y) coordinates of cursor within self._original_widget."""
|
|
433
|
+
if not hasattr(self._original_widget, "get_cursor_coords"):
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
left, right = self.padding_values(size, True)
|
|
437
|
+
if size:
|
|
438
|
+
maxvals = (size[0] - left - right,) + size[1:]
|
|
439
|
+
if maxvals[0] == 0:
|
|
440
|
+
return None
|
|
441
|
+
else:
|
|
442
|
+
maxvals = ()
|
|
443
|
+
|
|
444
|
+
coords = self._original_widget.get_cursor_coords(maxvals)
|
|
445
|
+
if coords is None:
|
|
446
|
+
return None
|
|
447
|
+
|
|
448
|
+
x, y = coords
|
|
449
|
+
return x + left, y
|
|
450
|
+
|
|
451
|
+
def move_cursor_to_coords(
|
|
452
|
+
self,
|
|
453
|
+
size: tuple[()] | tuple[int] | tuple[int, int],
|
|
454
|
+
x: int,
|
|
455
|
+
y: int,
|
|
456
|
+
) -> bool:
|
|
457
|
+
"""Set the cursor position with (x,y) coordinates of self._original_widget.
|
|
458
|
+
|
|
459
|
+
Returns True if move succeeded, False otherwise.
|
|
460
|
+
"""
|
|
461
|
+
if not hasattr(self._original_widget, "move_cursor_to_coords"):
|
|
462
|
+
return True
|
|
463
|
+
|
|
464
|
+
left, right = self.padding_values(size, True)
|
|
465
|
+
if size:
|
|
466
|
+
maxcol = size[0]
|
|
467
|
+
maxvals = (maxcol - left - right,) + size[1:]
|
|
468
|
+
else:
|
|
469
|
+
maxcol = self.pack((), True)[0]
|
|
470
|
+
maxvals = ()
|
|
471
|
+
|
|
472
|
+
if isinstance(x, int):
|
|
473
|
+
if x < left:
|
|
474
|
+
x = left
|
|
475
|
+
elif x >= maxcol - right:
|
|
476
|
+
x = maxcol - right - 1
|
|
477
|
+
x -= left
|
|
478
|
+
|
|
479
|
+
return self._original_widget.move_cursor_to_coords(maxvals, x, y)
|
|
480
|
+
|
|
481
|
+
def mouse_event(
|
|
482
|
+
self,
|
|
483
|
+
size: tuple[()] | tuple[int] | tuple[int, int],
|
|
484
|
+
event: str,
|
|
485
|
+
button: int,
|
|
486
|
+
col: int,
|
|
487
|
+
row: int,
|
|
488
|
+
focus: bool,
|
|
489
|
+
) -> bool | None:
|
|
490
|
+
"""Send mouse event if position is within self._original_widget."""
|
|
491
|
+
if not hasattr(self._original_widget, "mouse_event"):
|
|
492
|
+
return False
|
|
493
|
+
|
|
494
|
+
left, right = self.padding_values(size, focus)
|
|
495
|
+
if size:
|
|
496
|
+
maxcol = size[0]
|
|
497
|
+
if col < left or col >= maxcol - right:
|
|
498
|
+
return False
|
|
499
|
+
maxvals = (maxcol - left - right,) + size[1:]
|
|
500
|
+
else:
|
|
501
|
+
maxvals = ()
|
|
502
|
+
|
|
503
|
+
return self._original_widget.mouse_event(maxvals, event, button, col - left, row, focus)
|
|
504
|
+
|
|
505
|
+
def get_pref_col(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> int | None:
|
|
506
|
+
"""Return the preferred column from self._original_widget, or None."""
|
|
507
|
+
if not hasattr(self._original_widget, "get_pref_col"):
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
left, right = self.padding_values(size, True)
|
|
511
|
+
if size:
|
|
512
|
+
maxvals = (size[0] - left - right,) + size[1:]
|
|
513
|
+
else:
|
|
514
|
+
maxvals = ()
|
|
515
|
+
|
|
516
|
+
x = self._original_widget.get_pref_col(maxvals)
|
|
517
|
+
if isinstance(x, int):
|
|
518
|
+
return x + left
|
|
519
|
+
return x
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def calculate_left_right_padding(
|
|
523
|
+
maxcol: int,
|
|
524
|
+
align_type: Literal["left", "center", "right"] | Align,
|
|
525
|
+
align_amount: int,
|
|
526
|
+
width_type: Literal["fixed", "relative", "clip", "given", WHSettings.RELATIVE, WHSettings.CLIP, WHSettings.GIVEN],
|
|
527
|
+
width_amount: int,
|
|
528
|
+
min_width: int | None,
|
|
529
|
+
left: int,
|
|
530
|
+
right: int,
|
|
531
|
+
) -> tuple[int, int]:
|
|
532
|
+
"""
|
|
533
|
+
Return the amount of padding (or clipping) on the left and
|
|
534
|
+
right part of maxcol columns to satisfy the following:
|
|
535
|
+
|
|
536
|
+
align_type -- 'left', 'center', 'right', 'relative'
|
|
537
|
+
align_amount -- a percentage when align_type=='relative'
|
|
538
|
+
width_type -- 'fixed', 'relative', 'clip'
|
|
539
|
+
width_amount -- a percentage when width_type=='relative'
|
|
540
|
+
otherwise equal to the width of the widget
|
|
541
|
+
min_width -- a desired minimum width for the widget or None
|
|
542
|
+
left -- a fixed number of columns to pad on the left
|
|
543
|
+
right -- a fixed number of columns to pad on the right
|
|
544
|
+
|
|
545
|
+
>>> clrp = calculate_left_right_padding
|
|
546
|
+
>>> clrp(15, 'left', 0, 'given', 10, None, 2, 0)
|
|
547
|
+
(2, 3)
|
|
548
|
+
>>> clrp(15, 'relative', 0, 'given', 10, None, 2, 0)
|
|
549
|
+
(2, 3)
|
|
550
|
+
>>> clrp(15, 'relative', 100, 'given', 10, None, 2, 0)
|
|
551
|
+
(5, 0)
|
|
552
|
+
>>> clrp(15, 'center', 0, 'given', 4, None, 2, 0)
|
|
553
|
+
(6, 5)
|
|
554
|
+
>>> clrp(15, 'left', 0, 'clip', 18, None, 0, 0)
|
|
555
|
+
(0, -3)
|
|
556
|
+
>>> clrp(15, 'right', 0, 'clip', 18, None, 0, -1)
|
|
557
|
+
(-2, -1)
|
|
558
|
+
>>> clrp(15, 'center', 0, 'given', 18, None, 2, 0)
|
|
559
|
+
(0, 0)
|
|
560
|
+
>>> clrp(20, 'left', 0, 'relative', 60, None, 0, 0)
|
|
561
|
+
(0, 8)
|
|
562
|
+
>>> clrp(20, 'relative', 30, 'relative', 60, None, 0, 0)
|
|
563
|
+
(2, 6)
|
|
564
|
+
>>> clrp(20, 'relative', 30, 'relative', 60, 14, 0, 0)
|
|
565
|
+
(2, 4)
|
|
566
|
+
"""
|
|
567
|
+
if width_type == WHSettings.RELATIVE:
|
|
568
|
+
maxwidth = max(maxcol - left - right, 0)
|
|
569
|
+
width = int(maxwidth * width_amount / 100 + 0.5)
|
|
570
|
+
if min_width is not None:
|
|
571
|
+
width = max(width, min_width)
|
|
572
|
+
else:
|
|
573
|
+
width = width_amount
|
|
574
|
+
|
|
575
|
+
align = {Align.LEFT: 0, Align.CENTER: 50, Align.RIGHT: 100}.get(align_type, align_amount)
|
|
576
|
+
|
|
577
|
+
# add the remainder of left/right the padding
|
|
578
|
+
padding = maxcol - width - left - right
|
|
579
|
+
right += int_scale(100 - align, 101, padding + 1)
|
|
580
|
+
left = maxcol - width - right
|
|
581
|
+
|
|
582
|
+
# reduce padding if we are clipping an edge
|
|
583
|
+
if right < 0 < left:
|
|
584
|
+
shift = min(left, -right)
|
|
585
|
+
left -= shift
|
|
586
|
+
right += shift
|
|
587
|
+
elif left < 0 < right:
|
|
588
|
+
shift = min(right, -left)
|
|
589
|
+
right -= shift
|
|
590
|
+
left += shift
|
|
591
|
+
|
|
592
|
+
# only clip if width_type == 'clip'
|
|
593
|
+
if width_type != WHSettings.CLIP and (left < 0 or right < 0):
|
|
594
|
+
left = max(left, 0)
|
|
595
|
+
right = max(right, 0)
|
|
596
|
+
|
|
597
|
+
return left, right
|