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/popup.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Urwid Window-Icon-Menu-Pointer-style widget classes
|
|
2
|
+
# Copyright (C) 2004-2011 Ian Ward
|
|
3
|
+
#
|
|
4
|
+
# This library is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
6
|
+
# License as published by the Free Software Foundation; either
|
|
7
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+
# Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
15
|
+
# License along with this library; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
17
|
+
#
|
|
18
|
+
# Urwid web site: https://urwid.org/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import typing
|
|
24
|
+
|
|
25
|
+
from urwid.canvas import CompositeCanvas
|
|
26
|
+
|
|
27
|
+
from .constants import Sizing
|
|
28
|
+
from .overlay import Overlay
|
|
29
|
+
from .widget import delegate_to_widget_mixin
|
|
30
|
+
from .widget_decoration import WidgetDecoration
|
|
31
|
+
|
|
32
|
+
if typing.TYPE_CHECKING:
|
|
33
|
+
from typing_extensions import TypedDict
|
|
34
|
+
|
|
35
|
+
from urwid.canvas import Canvas
|
|
36
|
+
|
|
37
|
+
from .widget import Widget
|
|
38
|
+
|
|
39
|
+
class PopUpParametersModel(TypedDict):
|
|
40
|
+
left: int
|
|
41
|
+
top: int
|
|
42
|
+
overlay_width: int
|
|
43
|
+
overlay_height: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
WrappedWidget = typing.TypeVar("WrappedWidget")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PopUpLauncher(delegate_to_widget_mixin("_original_widget"), WidgetDecoration[WrappedWidget]):
|
|
50
|
+
def __init__(self, original_widget: [WrappedWidget]) -> None:
|
|
51
|
+
super().__init__(original_widget)
|
|
52
|
+
self._pop_up_widget = None
|
|
53
|
+
|
|
54
|
+
def create_pop_up(self) -> Widget:
|
|
55
|
+
"""
|
|
56
|
+
Subclass must override this method and return a widget
|
|
57
|
+
to be used for the pop-up. This method is called once each time
|
|
58
|
+
the pop-up is opened.
|
|
59
|
+
"""
|
|
60
|
+
raise NotImplementedError("Subclass must override this method")
|
|
61
|
+
|
|
62
|
+
def get_pop_up_parameters(self) -> PopUpParametersModel:
|
|
63
|
+
"""
|
|
64
|
+
Subclass must override this method and have it return a dict, eg:
|
|
65
|
+
|
|
66
|
+
{'left':0, 'top':1, 'overlay_width':30, 'overlay_height':4}
|
|
67
|
+
|
|
68
|
+
This method is called each time this widget is rendered.
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError("Subclass must override this method")
|
|
71
|
+
|
|
72
|
+
def open_pop_up(self) -> None:
|
|
73
|
+
self._pop_up_widget = self.create_pop_up()
|
|
74
|
+
self._invalidate()
|
|
75
|
+
|
|
76
|
+
def close_pop_up(self) -> None:
|
|
77
|
+
self._pop_up_widget = None
|
|
78
|
+
self._invalidate()
|
|
79
|
+
|
|
80
|
+
def render(self, size, focus: bool = False) -> CompositeCanvas | Canvas:
|
|
81
|
+
canv = super().render(size, focus)
|
|
82
|
+
if self._pop_up_widget:
|
|
83
|
+
canv = CompositeCanvas(canv)
|
|
84
|
+
canv.set_pop_up(self._pop_up_widget, **self.get_pop_up_parameters())
|
|
85
|
+
return canv
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class PopUpTarget(WidgetDecoration[WrappedWidget]):
|
|
89
|
+
# FIXME: this whole class is a terrible hack and must be fixed
|
|
90
|
+
# when layout and rendering are separated
|
|
91
|
+
_sizing = frozenset((Sizing.BOX,))
|
|
92
|
+
_selectable = True
|
|
93
|
+
|
|
94
|
+
def __init__(self, original_widget: WrappedWidget) -> None:
|
|
95
|
+
super().__init__(original_widget)
|
|
96
|
+
self._pop_up = None
|
|
97
|
+
self._current_widget = self._original_widget
|
|
98
|
+
|
|
99
|
+
def _update_overlay(self, size: tuple[int, int], focus: bool) -> None:
|
|
100
|
+
canv = self._original_widget.render(size, focus=focus)
|
|
101
|
+
self._cache_original_canvas = canv # imperfect performance hack
|
|
102
|
+
pop_up = canv.get_pop_up()
|
|
103
|
+
if pop_up:
|
|
104
|
+
left, top, (w, overlay_width, overlay_height) = pop_up
|
|
105
|
+
if self._pop_up != w:
|
|
106
|
+
self._pop_up = w
|
|
107
|
+
self._current_widget = Overlay(
|
|
108
|
+
w,
|
|
109
|
+
self._original_widget,
|
|
110
|
+
("fixed left", left),
|
|
111
|
+
overlay_width,
|
|
112
|
+
("fixed top", top),
|
|
113
|
+
overlay_height,
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
self._current_widget.set_overlay_parameters(
|
|
117
|
+
("fixed left", left),
|
|
118
|
+
overlay_width,
|
|
119
|
+
("fixed top", top),
|
|
120
|
+
overlay_height,
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
self._pop_up = None
|
|
124
|
+
self._current_widget = self._original_widget
|
|
125
|
+
|
|
126
|
+
def render(self, size: tuple[int, int], focus: bool = False) -> Canvas:
|
|
127
|
+
self._update_overlay(size, focus)
|
|
128
|
+
return self._current_widget.render(size, focus=focus)
|
|
129
|
+
|
|
130
|
+
def get_cursor_coords(self, size: tuple[int, int]) -> tuple[int, int] | None:
|
|
131
|
+
self._update_overlay(size, True)
|
|
132
|
+
return self._current_widget.get_cursor_coords(size)
|
|
133
|
+
|
|
134
|
+
def get_pref_col(self, size: tuple[int, int]) -> int:
|
|
135
|
+
self._update_overlay(size, True)
|
|
136
|
+
return self._current_widget.get_pref_col(size)
|
|
137
|
+
|
|
138
|
+
def keypress(self, size: tuple[int, int], key: str) -> str | None:
|
|
139
|
+
self._update_overlay(size, True)
|
|
140
|
+
return self._current_widget.keypress(size, key)
|
|
141
|
+
|
|
142
|
+
def move_cursor_to_coords(self, size: tuple[int, int], x: int, y: int):
|
|
143
|
+
self._update_overlay(size, True)
|
|
144
|
+
return self._current_widget.move_cursor_to_coords(size, x, y)
|
|
145
|
+
|
|
146
|
+
def mouse_event(
|
|
147
|
+
self,
|
|
148
|
+
size: tuple[int, int],
|
|
149
|
+
event: str,
|
|
150
|
+
button: int,
|
|
151
|
+
col: int,
|
|
152
|
+
row: int,
|
|
153
|
+
focus: bool,
|
|
154
|
+
) -> bool | None:
|
|
155
|
+
self._update_overlay(size, focus)
|
|
156
|
+
return self._current_widget.mouse_event(size, event, button, col, row, focus)
|
|
157
|
+
|
|
158
|
+
def pack(self, size: tuple[int, int] | None = None, focus: bool = False) -> tuple[int, int]:
|
|
159
|
+
self._update_overlay(size, focus)
|
|
160
|
+
return self._current_widget.pack(size)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _test():
|
|
164
|
+
import doctest
|
|
165
|
+
|
|
166
|
+
doctest.testmod()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
_test()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from .constants import BAR_SYMBOLS, Align, Sizing, WrapMode
|
|
7
|
+
from .text import Text
|
|
8
|
+
from .widget import Widget
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
from urwid.canvas import TextCanvas
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProgressBar(Widget):
|
|
15
|
+
_sizing = frozenset([Sizing.FLOW])
|
|
16
|
+
|
|
17
|
+
eighths = BAR_SYMBOLS.HORISONTAL[:8] # Full width line is made by style
|
|
18
|
+
|
|
19
|
+
text_align = Align.CENTER
|
|
20
|
+
|
|
21
|
+
def __init__(self, normal, complete, current: int = 0, done: int = 100, satt=None):
|
|
22
|
+
"""
|
|
23
|
+
:param normal: display attribute for incomplete part of progress bar
|
|
24
|
+
:param complete: display attribute for complete part of progress bar
|
|
25
|
+
:param current: current progress
|
|
26
|
+
:param done: progress amount at 100%
|
|
27
|
+
:param satt: display attribute for smoothed part of bar where the
|
|
28
|
+
foreground of satt corresponds to the normal part and the
|
|
29
|
+
background corresponds to the complete part.
|
|
30
|
+
If satt is ``None`` then no smoothing will be done.
|
|
31
|
+
|
|
32
|
+
>>> from urwid import LineBox
|
|
33
|
+
>>> pb = ProgressBar('a', 'b')
|
|
34
|
+
>>> pb
|
|
35
|
+
<ProgressBar flow widget>
|
|
36
|
+
>>> print(pb.get_text())
|
|
37
|
+
0 %
|
|
38
|
+
>>> pb.set_completion(34.42)
|
|
39
|
+
>>> print(pb.get_text())
|
|
40
|
+
34 %
|
|
41
|
+
>>> class CustomProgressBar(ProgressBar):
|
|
42
|
+
... def get_text(self):
|
|
43
|
+
... return u'Foobar'
|
|
44
|
+
>>> cpb = CustomProgressBar('a', 'b')
|
|
45
|
+
>>> print(cpb.get_text())
|
|
46
|
+
Foobar
|
|
47
|
+
>>> for x in range(101):
|
|
48
|
+
... cpb.set_completion(x)
|
|
49
|
+
... s = cpb.render((10, ))
|
|
50
|
+
>>> cpb2 = CustomProgressBar('a', 'b', satt='c')
|
|
51
|
+
>>> for x in range(101):
|
|
52
|
+
... cpb2.set_completion(x)
|
|
53
|
+
... s = cpb2.render((10, ))
|
|
54
|
+
>>> pb = ProgressBar('a', 'b', satt='c')
|
|
55
|
+
>>> pb.set_completion(34.56)
|
|
56
|
+
>>> print(LineBox(pb).render((20,)))
|
|
57
|
+
┌──────────────────┐
|
|
58
|
+
│ ▏34 % │
|
|
59
|
+
└──────────────────┘
|
|
60
|
+
"""
|
|
61
|
+
super().__init__()
|
|
62
|
+
self.normal = normal
|
|
63
|
+
self.complete = complete
|
|
64
|
+
self._current = current
|
|
65
|
+
self._done = done
|
|
66
|
+
self.satt = satt
|
|
67
|
+
|
|
68
|
+
def set_completion(self, current: int) -> None:
|
|
69
|
+
"""
|
|
70
|
+
current -- current progress
|
|
71
|
+
"""
|
|
72
|
+
self._current = current
|
|
73
|
+
self._invalidate()
|
|
74
|
+
|
|
75
|
+
current = property(lambda self: self._current, set_completion)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def done(self):
|
|
79
|
+
return self._done
|
|
80
|
+
|
|
81
|
+
@done.setter
|
|
82
|
+
def done(self, done):
|
|
83
|
+
"""
|
|
84
|
+
done -- progress amount at 100%
|
|
85
|
+
"""
|
|
86
|
+
self._done = done
|
|
87
|
+
self._invalidate()
|
|
88
|
+
|
|
89
|
+
def _set_done(self, done):
|
|
90
|
+
warnings.warn(
|
|
91
|
+
f"Method `{self.__class__.__name__}._set_done` is deprecated, "
|
|
92
|
+
f"please use property `{self.__class__.__name__}.done`",
|
|
93
|
+
DeprecationWarning,
|
|
94
|
+
stacklevel=2,
|
|
95
|
+
)
|
|
96
|
+
self.done = done
|
|
97
|
+
|
|
98
|
+
def rows(self, size: tuple[int], focus: bool = False) -> int:
|
|
99
|
+
return 1
|
|
100
|
+
|
|
101
|
+
def get_text(self) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Return the progress bar percentage text.
|
|
104
|
+
You can override this method to display custom text.
|
|
105
|
+
"""
|
|
106
|
+
percent = min(100, max(0, int(self.current * 100 / self.done)))
|
|
107
|
+
return f"{percent!s} %"
|
|
108
|
+
|
|
109
|
+
def render(self, size: tuple[int], focus: bool = False) -> TextCanvas:
|
|
110
|
+
"""
|
|
111
|
+
Render the progress bar.
|
|
112
|
+
"""
|
|
113
|
+
# pylint: disable=protected-access
|
|
114
|
+
(maxcol,) = size
|
|
115
|
+
c = Text(self.get_text(), self.text_align, WrapMode.CLIP).render((maxcol,))
|
|
116
|
+
|
|
117
|
+
cf = float(self.current) * maxcol / self.done
|
|
118
|
+
ccol_dirty = int(cf)
|
|
119
|
+
ccol = len(c._text[0][:ccol_dirty].decode("utf-8", "ignore").encode("utf-8"))
|
|
120
|
+
cs = 0
|
|
121
|
+
if self.satt is not None:
|
|
122
|
+
cs = int((cf - ccol) * 8)
|
|
123
|
+
if ccol < 0 or (ccol == cs == 0):
|
|
124
|
+
c._attr = [[(self.normal, maxcol)]]
|
|
125
|
+
elif ccol >= maxcol:
|
|
126
|
+
c._attr = [[(self.complete, maxcol)]]
|
|
127
|
+
elif cs and c._text[0][ccol] == 32:
|
|
128
|
+
t = c._text[0]
|
|
129
|
+
cenc = self.eighths[cs].encode("utf-8")
|
|
130
|
+
c._text[0] = t[:ccol] + cenc + t[ccol + 1 :]
|
|
131
|
+
a = []
|
|
132
|
+
if ccol > 0:
|
|
133
|
+
a.append((self.complete, ccol))
|
|
134
|
+
a.append((self.satt, len(cenc)))
|
|
135
|
+
if maxcol - ccol - 1 > 0:
|
|
136
|
+
a.append((self.normal, maxcol - ccol - 1))
|
|
137
|
+
c._attr = [a]
|
|
138
|
+
c._cs = [[(None, len(c._text[0]))]]
|
|
139
|
+
else:
|
|
140
|
+
c._attr = [[(self.complete, ccol), (self.normal, maxcol - ccol)]]
|
|
141
|
+
return c
|