console-window 1.0.2__tar.gz → 1.0.4__tar.gz
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.
- {console_window-1.0.2 → console_window-1.0.4}/PKG-INFO +4 -4
- {console_window-1.0.2 → console_window-1.0.4}/README.md +3 -3
- {console_window-1.0.2 → console_window-1.0.4}/console_window/ConsoleWindow.py +48 -34
- {console_window-1.0.2 → console_window-1.0.4}/pyproject.toml +1 -1
- {console_window-1.0.2 → console_window-1.0.4}/.gitignore +0 -0
- {console_window-1.0.2 → console_window-1.0.4}/LICENSE +0 -0
- {console_window-1.0.2 → console_window-1.0.4}/console_window/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: console-window
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: A robust curses wrapper for creating Text User Interfaces (TUIs).
|
|
5
5
|
Author-email: Joe Defen <joedef@duck.com>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -58,8 +58,7 @@ def main_app_loop(stdscr):
|
|
|
58
58
|
spin.add_key('pick_mode', 'p - toggle pick mode', vals=[False, True])
|
|
59
59
|
spin.add_key('pick_size', 's - #rows in pick', vals=[1, 2, 3])
|
|
60
60
|
spin.add_key('name', 'n - select name', prompt='Provide Your Name:')
|
|
61
|
-
|
|
62
|
-
other_keys = {ord('q'), ord('Q')}
|
|
61
|
+
spin.add_key('quit', 'q,Q - quit the app', category='action', keys={ord('Q'), ord('q')})
|
|
63
62
|
|
|
64
63
|
# 2. Initialize Window
|
|
65
64
|
win = ConsoleWindow(head_line=True, keys=spin.keys^other_keys)
|
|
@@ -91,7 +90,8 @@ def main_app_loop(stdscr):
|
|
|
91
90
|
# Check if OptionSpinner can handle the key (p, s, n, ?)
|
|
92
91
|
spin.do_key(key, win)
|
|
93
92
|
|
|
94
|
-
if
|
|
93
|
+
if opts.quit:
|
|
94
|
+
opts.quit = False
|
|
95
95
|
break # Exit the loop
|
|
96
96
|
|
|
97
97
|
win.clear()
|
|
@@ -44,8 +44,7 @@ def main_app_loop(stdscr):
|
|
|
44
44
|
spin.add_key('pick_mode', 'p - toggle pick mode', vals=[False, True])
|
|
45
45
|
spin.add_key('pick_size', 's - #rows in pick', vals=[1, 2, 3])
|
|
46
46
|
spin.add_key('name', 'n - select name', prompt='Provide Your Name:')
|
|
47
|
-
|
|
48
|
-
other_keys = {ord('q'), ord('Q')}
|
|
47
|
+
spin.add_key('quit', 'q,Q - quit the app', category='action', keys={ord('Q'), ord('q')})
|
|
49
48
|
|
|
50
49
|
# 2. Initialize Window
|
|
51
50
|
win = ConsoleWindow(head_line=True, keys=spin.keys^other_keys)
|
|
@@ -77,7 +76,8 @@ def main_app_loop(stdscr):
|
|
|
77
76
|
# Check if OptionSpinner can handle the key (p, s, n, ?)
|
|
78
77
|
spin.do_key(key, win)
|
|
79
78
|
|
|
80
|
-
if
|
|
79
|
+
if opts.quit:
|
|
80
|
+
opts.quit = False
|
|
81
81
|
break # Exit the loop
|
|
82
82
|
|
|
83
83
|
win.clear()
|
|
@@ -10,7 +10,9 @@ a set of key-driven application settings.
|
|
|
10
10
|
"""
|
|
11
11
|
# pylint: disable=too-many-instance-attributes,too-many-arguments
|
|
12
12
|
# pylint: disable=invalid-name,broad-except,too-many-branches,global-statement
|
|
13
|
+
# pylint: disable=line-too-long,too-many-statements,too-many-locals
|
|
13
14
|
|
|
15
|
+
import sys
|
|
14
16
|
import traceback
|
|
15
17
|
import atexit
|
|
16
18
|
import signal
|
|
@@ -77,6 +79,7 @@ class OptionSpinner:
|
|
|
77
79
|
vals=None,
|
|
78
80
|
prompt=None,
|
|
79
81
|
comments=[],
|
|
82
|
+
category=None,
|
|
80
83
|
)
|
|
81
84
|
|
|
82
85
|
def get_value(self, attr, coerce=False):
|
|
@@ -147,7 +150,8 @@ class OptionSpinner:
|
|
|
147
150
|
ns.keys = [ord(ns.descr[0])]
|
|
148
151
|
self._register(ns)
|
|
149
152
|
|
|
150
|
-
def add_key(self, attr, descr, obj=None, vals=None, prompt=None,
|
|
153
|
+
def add_key(self, attr, descr, obj=None, vals=None, prompt=None,
|
|
154
|
+
keys=None, comments=None, category=None):
|
|
151
155
|
"""
|
|
152
156
|
Adds an option that is toggled by a key press.
|
|
153
157
|
|
|
@@ -164,6 +168,7 @@ class OptionSpinner:
|
|
|
164
168
|
that will trigger this option. If None, uses the first letter of
|
|
165
169
|
``descr``.
|
|
166
170
|
:param comments: Additional line(s) for the help screen item (string or list of strings).
|
|
171
|
+
:param category: (action, cycle, prompt)
|
|
167
172
|
:type attr: str
|
|
168
173
|
:type descr: str
|
|
169
174
|
:type obj: Any
|
|
@@ -171,9 +176,11 @@ class OptionSpinner:
|
|
|
171
176
|
:type prompt: str or None
|
|
172
177
|
:type keys: int or list or tuple or None
|
|
173
178
|
:type comments: str or list or tuple or None
|
|
179
|
+
:type category: str or None
|
|
174
180
|
:raises AssertionError: If both ``vals`` and ``prompt`` are provided, or neither is.
|
|
175
181
|
:raises AssertionError: If a key is already registered.
|
|
176
182
|
"""
|
|
183
|
+
|
|
177
184
|
ns = self._make_option_ns()
|
|
178
185
|
if keys:
|
|
179
186
|
ns.keys = list(keys) if isinstance(keys, (list, tuple, set)) else [keys]
|
|
@@ -186,8 +193,12 @@ class OptionSpinner:
|
|
|
186
193
|
ns.descr = descr
|
|
187
194
|
ns.attr = attr
|
|
188
195
|
ns.obj = obj if obj else self.default_obj
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
if vals:
|
|
197
|
+
ns.vals, ns.category = vals, 'cycle'
|
|
198
|
+
elif prompt:
|
|
199
|
+
ns.prompt, ns.category = prompt, 'prompt'
|
|
200
|
+
else:
|
|
201
|
+
ns.category = 'action'
|
|
191
202
|
self._register(ns)
|
|
192
203
|
|
|
193
204
|
@staticmethod
|
|
@@ -216,17 +227,19 @@ class OptionSpinner:
|
|
|
216
227
|
# get / coerce the current value
|
|
217
228
|
value = self.get_value(ns.attr)
|
|
218
229
|
assert value is not None, f'cannot get value of {repr(ns.attr)}'
|
|
219
|
-
choices = ns.vals if ns.vals else [value]
|
|
220
230
|
|
|
221
|
-
|
|
231
|
+
colon = '' if ns.category == 'action' else ':'
|
|
232
|
+
win.add_body(f'{ns.descr:>{self.align}}{colon} ')
|
|
222
233
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
shown =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
if ns.category in ('cycle', 'prompt'):
|
|
235
|
+
choices = ns.vals if ns.vals else [value]
|
|
236
|
+
for choice in choices:
|
|
237
|
+
shown = f'{choice}'
|
|
238
|
+
if isinstance(choice, bool):
|
|
239
|
+
shown = "ON" if choice else "off"
|
|
240
|
+
win.add_body(' ', resume=True)
|
|
241
|
+
win.add_body(shown, resume=True,
|
|
242
|
+
attr=curses.A_REVERSE if choice == value else None)
|
|
230
243
|
|
|
231
244
|
for comment in ns.comments:
|
|
232
245
|
win.add_body(f'{"":>{self.align}}: {comment}')
|
|
@@ -249,11 +262,14 @@ class OptionSpinner:
|
|
|
249
262
|
if ns is None:
|
|
250
263
|
return None
|
|
251
264
|
value = self.get_value(ns.attr)
|
|
252
|
-
if ns.
|
|
265
|
+
if ns.category == 'cycle':
|
|
253
266
|
idx = ns.vals.index(value) if value in ns.vals else -1
|
|
254
267
|
value = ns.vals[(idx+1) % len(ns.vals)] # choose next
|
|
255
|
-
|
|
268
|
+
elif ns.category == 'prompt':
|
|
256
269
|
value = win.answer(prompt=ns.prompt, seed=str(value))
|
|
270
|
+
elif ns.category == 'action':
|
|
271
|
+
value = True
|
|
272
|
+
|
|
257
273
|
setattr(ns.obj, ns.attr, value)
|
|
258
274
|
return value
|
|
259
275
|
|
|
@@ -290,8 +306,8 @@ class ConsoleWindow:
|
|
|
290
306
|
:param pick_mode: If True, enables item highlighting/selection mode in the body.
|
|
291
307
|
:param pick_size: The number of rows to be highlighted as a single 'pick' unit.
|
|
292
308
|
:param mod_pick: An optional callable to modify the highlighted text before drawing.
|
|
293
|
-
:param ctrl_c_terminates: If True (default), Ctrl-C terminates the application
|
|
294
|
-
(SIGINT is ignored). If False, Ctrl-C is caught by
|
|
309
|
+
:param ctrl_c_terminates: If True (default), Ctrl-C terminates the application
|
|
310
|
+
(SIGINT is ignored). If False, Ctrl-C is caught by
|
|
295
311
|
a signal handler and reported as key code 3.
|
|
296
312
|
:type head_line: bool
|
|
297
313
|
:type head_rows: int
|
|
@@ -804,7 +820,7 @@ class ConsoleWindow:
|
|
|
804
820
|
"""
|
|
805
821
|
input_string = list(seed)
|
|
806
822
|
cursor_pos = len(input_string)
|
|
807
|
-
|
|
823
|
+
|
|
808
824
|
if self.rows < 3 or self.cols < 30:
|
|
809
825
|
return seed
|
|
810
826
|
|
|
@@ -819,29 +835,29 @@ class ConsoleWindow:
|
|
|
819
835
|
self.scr.clear()
|
|
820
836
|
rectangle(self.scr, row0, col0, row9, col0 + text_win_width + 1)
|
|
821
837
|
self.scr.addstr(row0, col0 + 1, prompt[:text_win_width])
|
|
822
|
-
|
|
838
|
+
|
|
823
839
|
# Calculate the visible portion of the string
|
|
824
840
|
start_pos = max(0, cursor_pos - text_win_width + 1)
|
|
825
841
|
end_pos = start_pos + text_win_width
|
|
826
842
|
display_str = "".join(input_string[start_pos:end_pos])
|
|
827
|
-
|
|
843
|
+
|
|
828
844
|
self.scr.addstr(row0 + 1, col0 + 1, display_str)
|
|
829
|
-
|
|
845
|
+
|
|
830
846
|
# Position the cursor
|
|
831
847
|
display_cursor_pos = cursor_pos - start_pos
|
|
832
848
|
self.scr.move(row0 + 1, col0 + 1 + display_cursor_pos)
|
|
833
|
-
|
|
849
|
+
|
|
834
850
|
ending = 'Press ENTER to submit'[:text_win_width]
|
|
835
851
|
self.scr.addstr(row9, col0 + 1 + text_win_width - len(ending), ending)
|
|
836
852
|
self.scr.refresh()
|
|
837
853
|
curses.curs_set(2)
|
|
838
|
-
|
|
854
|
+
|
|
839
855
|
key = self.scr.getch()
|
|
840
|
-
|
|
856
|
+
|
|
841
857
|
if key in [10, 13]: # Enter key
|
|
842
858
|
curses.curs_set(0) # Restore cursor visibility
|
|
843
859
|
return "".join(input_string)
|
|
844
|
-
|
|
860
|
+
if key in [curses.KEY_BACKSPACE, 127, 8]: # Backspace
|
|
845
861
|
if cursor_pos > 0:
|
|
846
862
|
input_string.pop(cursor_pos - 1)
|
|
847
863
|
cursor_pos -= 1
|
|
@@ -899,7 +915,7 @@ class ConsoleWindow:
|
|
|
899
915
|
self.scr.addstr(row9, col0+1+width-len(ending), ending)
|
|
900
916
|
self.scr.refresh()
|
|
901
917
|
pad.refresh(0, 0, row0+1, col0+1, row9-1, col9-1)
|
|
902
|
-
|
|
918
|
+
|
|
903
919
|
# Use a dummy Textbox with a dummy window to block until Enter is pressed
|
|
904
920
|
curses.curs_set(0) # ensure cursor is off
|
|
905
921
|
Textbox(win).edit(mod_key).strip()
|
|
@@ -1005,7 +1021,6 @@ def no_runner():
|
|
|
1005
1021
|
|
|
1006
1022
|
if __name__ == '__main__':
|
|
1007
1023
|
def main():
|
|
1008
|
-
import sys
|
|
1009
1024
|
"""Test program"""
|
|
1010
1025
|
def do_key(key):
|
|
1011
1026
|
nonlocal spin, win, opts, pick_values
|
|
@@ -1016,20 +1031,21 @@ if __name__ == '__main__':
|
|
|
1016
1031
|
opts.prev_pick = pick_values[win.pick_pos//win.pick_size]
|
|
1017
1032
|
elif key == ord('n'):
|
|
1018
1033
|
win.alert(title='Info', message=f'got: {value}')
|
|
1019
|
-
elif
|
|
1034
|
+
elif opts.quit:
|
|
1035
|
+
opts.quit = False
|
|
1020
1036
|
sys.exit(key)
|
|
1021
1037
|
return value
|
|
1022
1038
|
|
|
1023
1039
|
spin = OptionSpinner()
|
|
1024
1040
|
spin.add_key('help_mode', '? - toggle help screen', vals=[False, True])
|
|
1025
|
-
spin.add_key('pick_mode', 'p -
|
|
1041
|
+
spin.add_key('pick_mode', 'p - toggle pick mode, turn off to pick current line', vals=[False, True])
|
|
1026
1042
|
spin.add_key('pick_size', 's - #rows in pick', vals=[1, 2, 3])
|
|
1027
1043
|
spin.add_key('name', 'n - select name', prompt='Provide Your Name:')
|
|
1028
1044
|
spin.add_key('mult', 'm - row multiplier', vals=[0.5, 0.9, 1.0, 1.1, 2, 4, 16])
|
|
1045
|
+
spin.add_key('quit', 'q,CTL-C - quit the app', category='action', keys={0x3, ord('q')})
|
|
1029
1046
|
opts = spin.default_obj
|
|
1030
|
-
other_keys = {0x3, ord('q')}
|
|
1031
1047
|
|
|
1032
|
-
win = ConsoleWindow(head_line=True, keys=spin.keys
|
|
1048
|
+
win = ConsoleWindow(head_line=True, keys=spin.keys,
|
|
1033
1049
|
ctrl_c_terminates=False, body_rows=4000)
|
|
1034
1050
|
opts.name = ""
|
|
1035
1051
|
opts.prev_pick = 'n/a'
|
|
@@ -1041,12 +1057,10 @@ if __name__ == '__main__':
|
|
|
1041
1057
|
win.set_pick_mode(False)
|
|
1042
1058
|
spin.show_help_nav_keys(win)
|
|
1043
1059
|
spin.show_help_body(win)
|
|
1044
|
-
win.put_body('Other Keys:', curses.A_UNDERLINE)
|
|
1045
|
-
win.put_body(' q, CTRL-C: quit program')
|
|
1046
1060
|
else:
|
|
1047
1061
|
win.set_pick_mode(opts.pick_mode, opts.pick_size)
|
|
1048
1062
|
win.add_header(f'{time.monotonic():.3f} [p]ick={opts.pick_mode}'
|
|
1049
|
-
|
|
1063
|
+
+ f' s:#rowsInPick={opts.pick_size} [n]ame [m]ult={opts.pick_size} ?=help [q]uit')
|
|
1050
1064
|
win.add_header(f'Header: {loop} name="{opts.name}" {opts.prev_pick=}')
|
|
1051
1065
|
pick_values = []
|
|
1052
1066
|
for idx, line in enumerate(range(body_size//opts.pick_size)):
|
|
@@ -1068,4 +1082,4 @@ if __name__ == '__main__':
|
|
|
1068
1082
|
print("exception:", str(exce))
|
|
1069
1083
|
print(traceback.format_exc())
|
|
1070
1084
|
if dump_str:
|
|
1071
|
-
print(dump_str)
|
|
1085
|
+
print(dump_str)
|
|
@@ -23,7 +23,7 @@ name = "console_window"
|
|
|
23
23
|
# UNIFIED PROJECT METADATA (Replaces [tool.flit.metadata])
|
|
24
24
|
[project]
|
|
25
25
|
name = "console-window"
|
|
26
|
-
version = "1.0.
|
|
26
|
+
version = "1.0.4"
|
|
27
27
|
description = "A robust curses wrapper for creating Text User Interfaces (TUIs)."
|
|
28
28
|
readme = "README.md"
|
|
29
29
|
requires-python = ">=3.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|