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,251 @@
|
|
|
1
|
+
# Urwid html fragment output wrapper for "screen shots"
|
|
2
|
+
# Copyright (C) 2004-2007 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
|
+
"""
|
|
22
|
+
HTML PRE-based UI implementation
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import html
|
|
28
|
+
import typing
|
|
29
|
+
|
|
30
|
+
from urwid import str_util
|
|
31
|
+
from urwid.event_loop import ExitMainLoop
|
|
32
|
+
|
|
33
|
+
from .common import AttrSpec, BaseScreen
|
|
34
|
+
|
|
35
|
+
if typing.TYPE_CHECKING:
|
|
36
|
+
from typing_extensions import Literal
|
|
37
|
+
|
|
38
|
+
from urwid import Canvas
|
|
39
|
+
|
|
40
|
+
# replace control characters with ?'s
|
|
41
|
+
_trans_table = "?" * 32 + "".join([chr(x) for x in range(32, 256)])
|
|
42
|
+
|
|
43
|
+
_default_foreground = "black"
|
|
44
|
+
_default_background = "light gray"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HtmlGeneratorSimulationError(Exception):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class HtmlGenerator(BaseScreen):
|
|
52
|
+
# class variables
|
|
53
|
+
fragments: typing.ClassVar[list[str]] = []
|
|
54
|
+
sizes: typing.ClassVar[list[tuple[int, int]]] = []
|
|
55
|
+
keys: typing.ClassVar[list[list[str] | tuple[list[str], list[int]]]] = []
|
|
56
|
+
started = True
|
|
57
|
+
|
|
58
|
+
def __init__(self):
|
|
59
|
+
super().__init__()
|
|
60
|
+
self.colors = 16
|
|
61
|
+
self.bright_is_bold = False # ignored
|
|
62
|
+
self.has_underline = True # ignored
|
|
63
|
+
self.register_palette_entry(None, _default_foreground, _default_background)
|
|
64
|
+
|
|
65
|
+
def set_terminal_properties(
|
|
66
|
+
self,
|
|
67
|
+
colors: int | None = None,
|
|
68
|
+
bright_is_bold: bool | None = None,
|
|
69
|
+
has_underline: bool | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
if colors is None:
|
|
72
|
+
colors = self.colors
|
|
73
|
+
if bright_is_bold is None:
|
|
74
|
+
bright_is_bold = self.bright_is_bold
|
|
75
|
+
if has_underline is None:
|
|
76
|
+
has_underline = self.has_underline
|
|
77
|
+
|
|
78
|
+
self.colors = colors
|
|
79
|
+
self.bright_is_bold = bright_is_bold
|
|
80
|
+
self.has_underline = has_underline
|
|
81
|
+
|
|
82
|
+
def set_input_timeouts(self, *args):
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def reset_default_terminal_palette(self, *args):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def draw_screen(self, size: tuple[int, int], canvas: Canvas):
|
|
89
|
+
"""Create an html fragment from the render object.
|
|
90
|
+
Append it to HtmlGenerator.fragments list.
|
|
91
|
+
"""
|
|
92
|
+
# collect output in l
|
|
93
|
+
lines = []
|
|
94
|
+
|
|
95
|
+
_cols, rows = size
|
|
96
|
+
|
|
97
|
+
if canvas.rows() != rows:
|
|
98
|
+
raise ValueError(rows)
|
|
99
|
+
|
|
100
|
+
if canvas.cursor is not None:
|
|
101
|
+
cx, cy = canvas.cursor
|
|
102
|
+
else:
|
|
103
|
+
cx = cy = None
|
|
104
|
+
|
|
105
|
+
y = -1
|
|
106
|
+
for row in canvas.content():
|
|
107
|
+
y += 1
|
|
108
|
+
col = 0
|
|
109
|
+
|
|
110
|
+
for a, _cs, run in row:
|
|
111
|
+
t_run = run.decode().translate(_trans_table)
|
|
112
|
+
if isinstance(a, AttrSpec):
|
|
113
|
+
aspec = a
|
|
114
|
+
else:
|
|
115
|
+
aspec = self._palette[a][{1: 1, 16: 0, 88: 2, 256: 3}[self.colors]]
|
|
116
|
+
|
|
117
|
+
if y == cy and col <= cx:
|
|
118
|
+
run_width = str_util.calc_width(t_run, 0, len(t_run))
|
|
119
|
+
if col + run_width > cx:
|
|
120
|
+
lines.append(html_span(t_run, aspec, cx - col))
|
|
121
|
+
else:
|
|
122
|
+
lines.append(html_span(t_run, aspec))
|
|
123
|
+
col += run_width
|
|
124
|
+
else:
|
|
125
|
+
lines.append(html_span(t_run, aspec))
|
|
126
|
+
|
|
127
|
+
lines.append("\n")
|
|
128
|
+
|
|
129
|
+
# add the fragment to the list
|
|
130
|
+
self.fragments.append(f"<pre>{''.join(lines)}</pre>")
|
|
131
|
+
|
|
132
|
+
def get_cols_rows(self):
|
|
133
|
+
"""Return the next screen size in HtmlGenerator.sizes."""
|
|
134
|
+
if not self.sizes:
|
|
135
|
+
raise HtmlGeneratorSimulationError("Ran out of screen sizes to return!")
|
|
136
|
+
return self.sizes.pop(0)
|
|
137
|
+
|
|
138
|
+
@typing.overload
|
|
139
|
+
def get_input(self, raw_keys: Literal[False]) -> list[str]: ...
|
|
140
|
+
|
|
141
|
+
@typing.overload
|
|
142
|
+
def get_input(self, raw_keys: Literal[True]) -> tuple[list[str], list[int]]: ...
|
|
143
|
+
|
|
144
|
+
def get_input(self, raw_keys: bool = False) -> list[str] | tuple[list[str], list[int]]:
|
|
145
|
+
"""Return the next list of keypresses in HtmlGenerator.keys."""
|
|
146
|
+
if not self.keys:
|
|
147
|
+
raise ExitMainLoop()
|
|
148
|
+
if raw_keys:
|
|
149
|
+
return (self.keys.pop(0), [])
|
|
150
|
+
return self.keys.pop(0)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
_default_aspec = AttrSpec(_default_foreground, _default_background)
|
|
154
|
+
(_d_fg_r, _d_fg_g, _d_fg_b, _d_bg_r, _d_bg_g, _d_bg_b) = _default_aspec.get_rgb_values()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def html_span(s, aspec, cursor: int = -1):
|
|
158
|
+
fg_r, fg_g, fg_b, bg_r, bg_g, bg_b = aspec.get_rgb_values()
|
|
159
|
+
# use real colours instead of default fg/bg
|
|
160
|
+
if fg_r is None:
|
|
161
|
+
fg_r, fg_g, fg_b = _d_fg_r, _d_fg_g, _d_fg_b
|
|
162
|
+
if bg_r is None:
|
|
163
|
+
bg_r, bg_g, bg_b = _d_bg_r, _d_bg_g, _d_bg_b
|
|
164
|
+
html_fg = f"#{fg_r:02x}{fg_g:02x}{fg_b:02x}"
|
|
165
|
+
html_bg = f"#{bg_r:02x}{bg_g:02x}{bg_b:02x}"
|
|
166
|
+
if aspec.standout:
|
|
167
|
+
html_fg, html_bg = html_bg, html_fg
|
|
168
|
+
extra = ";text-decoration:underline" * aspec.underline + ";font-weight:bold" * aspec.bold
|
|
169
|
+
|
|
170
|
+
def _span(fg: str, bg: str, string: str) -> str:
|
|
171
|
+
if not s:
|
|
172
|
+
return ""
|
|
173
|
+
return f'<span style="color:{fg};background:{bg}{extra}">{html_escape(string)}</span>'
|
|
174
|
+
|
|
175
|
+
if cursor >= 0:
|
|
176
|
+
c_off, _ign = str_util.calc_text_pos(s, 0, len(s), cursor)
|
|
177
|
+
c2_off = str_util.move_next_char(s, c_off, len(s))
|
|
178
|
+
return (
|
|
179
|
+
_span(html_fg, html_bg, s[:c_off])
|
|
180
|
+
+ _span(html_bg, html_fg, s[c_off:c2_off])
|
|
181
|
+
+ _span(html_fg, html_bg, s[c2_off:])
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return _span(html_fg, html_bg, s)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def html_escape(text: str) -> str:
|
|
188
|
+
"""Escape text so that it will be displayed safely within HTML"""
|
|
189
|
+
return html.escape(text)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def screenshot_init(sizes: list[tuple[int, int]], keys: list[list[str]]) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Replace curses_display.Screen and raw_display.Screen class with
|
|
195
|
+
HtmlGenerator.
|
|
196
|
+
|
|
197
|
+
Call this function before executing an application that uses
|
|
198
|
+
curses_display.Screen to have that code use HtmlGenerator instead.
|
|
199
|
+
|
|
200
|
+
sizes -- list of ( columns, rows ) tuples to be returned by each call
|
|
201
|
+
to HtmlGenerator.get_cols_rows()
|
|
202
|
+
keys -- list of lists of keys to be returned by each call to
|
|
203
|
+
HtmlGenerator.get_input()
|
|
204
|
+
|
|
205
|
+
Lists of keys may include "window resize" to force the application to
|
|
206
|
+
call get_cols_rows and read a new screen size.
|
|
207
|
+
|
|
208
|
+
For example, the following call will prepare an application to:
|
|
209
|
+
1. start in 80x25 with its first call to get_cols_rows()
|
|
210
|
+
2. take a screenshot when it calls draw_screen(..)
|
|
211
|
+
3. simulate 5 "down" keys from get_input()
|
|
212
|
+
4. take a screenshot when it calls draw_screen(..)
|
|
213
|
+
5. simulate keys "a", "b", "c" and a "window resize"
|
|
214
|
+
6. resize to 20x10 on its second call to get_cols_rows()
|
|
215
|
+
7. take a screenshot when it calls draw_screen(..)
|
|
216
|
+
8. simulate a "Q" keypress to quit the application
|
|
217
|
+
|
|
218
|
+
screenshot_init( [ (80,25), (20,10) ],
|
|
219
|
+
[ ["down"]*5, ["a","b","c","window resize"], ["Q"] ] )
|
|
220
|
+
"""
|
|
221
|
+
for row, col in sizes:
|
|
222
|
+
if not isinstance(row, int):
|
|
223
|
+
raise TypeError(f"sizes must be list[tuple[int, int]], with values >0 : {row!r}")
|
|
224
|
+
if row <= 0:
|
|
225
|
+
raise ValueError(f"sizes must be list[tuple[int, int]], with values >0 : {row!r}")
|
|
226
|
+
if not isinstance(col, int):
|
|
227
|
+
raise TypeError(f"sizes must be list[tuple[int, int]], with values >0 : {col!r}")
|
|
228
|
+
if col <= 0:
|
|
229
|
+
raise ValueError(f"sizes must be list[tuple[int, int]], with values >0 : {col!r}")
|
|
230
|
+
|
|
231
|
+
for line in keys:
|
|
232
|
+
if not isinstance(line, list):
|
|
233
|
+
raise TypeError(f"keys must be list[list[str]]: {line!r}")
|
|
234
|
+
for k in line:
|
|
235
|
+
if not isinstance(k, str):
|
|
236
|
+
raise TypeError(f"keys must be list[list[str]]: {k!r}")
|
|
237
|
+
|
|
238
|
+
from . import curses, raw
|
|
239
|
+
|
|
240
|
+
curses.Screen = HtmlGenerator
|
|
241
|
+
raw.Screen = HtmlGenerator
|
|
242
|
+
|
|
243
|
+
HtmlGenerator.sizes = sizes
|
|
244
|
+
HtmlGenerator.keys = keys
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def screenshot_collect():
|
|
248
|
+
"""Return screenshots as a list of HTML fragments."""
|
|
249
|
+
fragments = HtmlGenerator.fragments
|
|
250
|
+
HtmlGenerator.fragments = []
|
|
251
|
+
return fragments
|