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
@@ -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