console-window 1.0.2__tar.gz → 1.0.3__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.3}/PKG-INFO +4 -4
- {console_window-1.0.2 → console_window-1.0.3}/README.md +3 -3
- {console_window-1.0.2 → console_window-1.0.3}/console_window/ConsoleWindow.py +39 -26
- {console_window-1.0.2 → console_window-1.0.3}/pyproject.toml +1 -1
- {console_window-1.0.2 → console_window-1.0.3}/.gitignore +0 -0
- {console_window-1.0.2 → console_window-1.0.3}/LICENSE +0 -0
- {console_window-1.0.2 → console_window-1.0.3}/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.3
|
|
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
|
|
@@ -218,7 +229,8 @@ class OptionSpinner:
|
|
|
218
229
|
assert value is not None, f'cannot get value of {repr(ns.attr)}'
|
|
219
230
|
choices = ns.vals if ns.vals else [value]
|
|
220
231
|
|
|
221
|
-
|
|
232
|
+
colon = '' if ns.category == 'action' else ':'
|
|
233
|
+
win.add_body(f'{ns.descr:>{self.align}}{colon} ')
|
|
222
234
|
|
|
223
235
|
for choice in choices:
|
|
224
236
|
shown = f'{choice}'
|
|
@@ -249,11 +261,14 @@ class OptionSpinner:
|
|
|
249
261
|
if ns is None:
|
|
250
262
|
return None
|
|
251
263
|
value = self.get_value(ns.attr)
|
|
252
|
-
if ns.
|
|
264
|
+
if ns.category == 'cycle':
|
|
253
265
|
idx = ns.vals.index(value) if value in ns.vals else -1
|
|
254
266
|
value = ns.vals[(idx+1) % len(ns.vals)] # choose next
|
|
255
|
-
|
|
267
|
+
elif ns.category == 'prompt':
|
|
256
268
|
value = win.answer(prompt=ns.prompt, seed=str(value))
|
|
269
|
+
elif ns.category == 'action':
|
|
270
|
+
value = True
|
|
271
|
+
|
|
257
272
|
setattr(ns.obj, ns.attr, value)
|
|
258
273
|
return value
|
|
259
274
|
|
|
@@ -290,8 +305,8 @@ class ConsoleWindow:
|
|
|
290
305
|
:param pick_mode: If True, enables item highlighting/selection mode in the body.
|
|
291
306
|
:param pick_size: The number of rows to be highlighted as a single 'pick' unit.
|
|
292
307
|
: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
|
|
308
|
+
:param ctrl_c_terminates: If True (default), Ctrl-C terminates the application
|
|
309
|
+
(SIGINT is ignored). If False, Ctrl-C is caught by
|
|
295
310
|
a signal handler and reported as key code 3.
|
|
296
311
|
:type head_line: bool
|
|
297
312
|
:type head_rows: int
|
|
@@ -804,7 +819,7 @@ class ConsoleWindow:
|
|
|
804
819
|
"""
|
|
805
820
|
input_string = list(seed)
|
|
806
821
|
cursor_pos = len(input_string)
|
|
807
|
-
|
|
822
|
+
|
|
808
823
|
if self.rows < 3 or self.cols < 30:
|
|
809
824
|
return seed
|
|
810
825
|
|
|
@@ -819,29 +834,29 @@ class ConsoleWindow:
|
|
|
819
834
|
self.scr.clear()
|
|
820
835
|
rectangle(self.scr, row0, col0, row9, col0 + text_win_width + 1)
|
|
821
836
|
self.scr.addstr(row0, col0 + 1, prompt[:text_win_width])
|
|
822
|
-
|
|
837
|
+
|
|
823
838
|
# Calculate the visible portion of the string
|
|
824
839
|
start_pos = max(0, cursor_pos - text_win_width + 1)
|
|
825
840
|
end_pos = start_pos + text_win_width
|
|
826
841
|
display_str = "".join(input_string[start_pos:end_pos])
|
|
827
|
-
|
|
842
|
+
|
|
828
843
|
self.scr.addstr(row0 + 1, col0 + 1, display_str)
|
|
829
|
-
|
|
844
|
+
|
|
830
845
|
# Position the cursor
|
|
831
846
|
display_cursor_pos = cursor_pos - start_pos
|
|
832
847
|
self.scr.move(row0 + 1, col0 + 1 + display_cursor_pos)
|
|
833
|
-
|
|
848
|
+
|
|
834
849
|
ending = 'Press ENTER to submit'[:text_win_width]
|
|
835
850
|
self.scr.addstr(row9, col0 + 1 + text_win_width - len(ending), ending)
|
|
836
851
|
self.scr.refresh()
|
|
837
852
|
curses.curs_set(2)
|
|
838
|
-
|
|
853
|
+
|
|
839
854
|
key = self.scr.getch()
|
|
840
|
-
|
|
855
|
+
|
|
841
856
|
if key in [10, 13]: # Enter key
|
|
842
857
|
curses.curs_set(0) # Restore cursor visibility
|
|
843
858
|
return "".join(input_string)
|
|
844
|
-
|
|
859
|
+
if key in [curses.KEY_BACKSPACE, 127, 8]: # Backspace
|
|
845
860
|
if cursor_pos > 0:
|
|
846
861
|
input_string.pop(cursor_pos - 1)
|
|
847
862
|
cursor_pos -= 1
|
|
@@ -899,7 +914,7 @@ class ConsoleWindow:
|
|
|
899
914
|
self.scr.addstr(row9, col0+1+width-len(ending), ending)
|
|
900
915
|
self.scr.refresh()
|
|
901
916
|
pad.refresh(0, 0, row0+1, col0+1, row9-1, col9-1)
|
|
902
|
-
|
|
917
|
+
|
|
903
918
|
# Use a dummy Textbox with a dummy window to block until Enter is pressed
|
|
904
919
|
curses.curs_set(0) # ensure cursor is off
|
|
905
920
|
Textbox(win).edit(mod_key).strip()
|
|
@@ -1005,7 +1020,6 @@ def no_runner():
|
|
|
1005
1020
|
|
|
1006
1021
|
if __name__ == '__main__':
|
|
1007
1022
|
def main():
|
|
1008
|
-
import sys
|
|
1009
1023
|
"""Test program"""
|
|
1010
1024
|
def do_key(key):
|
|
1011
1025
|
nonlocal spin, win, opts, pick_values
|
|
@@ -1016,20 +1030,21 @@ if __name__ == '__main__':
|
|
|
1016
1030
|
opts.prev_pick = pick_values[win.pick_pos//win.pick_size]
|
|
1017
1031
|
elif key == ord('n'):
|
|
1018
1032
|
win.alert(title='Info', message=f'got: {value}')
|
|
1019
|
-
elif
|
|
1033
|
+
elif opts.quit:
|
|
1034
|
+
opts.quit = False
|
|
1020
1035
|
sys.exit(key)
|
|
1021
1036
|
return value
|
|
1022
1037
|
|
|
1023
1038
|
spin = OptionSpinner()
|
|
1024
1039
|
spin.add_key('help_mode', '? - toggle help screen', vals=[False, True])
|
|
1025
|
-
spin.add_key('pick_mode', 'p -
|
|
1040
|
+
spin.add_key('pick_mode', 'p - toggle pick mode, turn off to pick current line', vals=[False, True])
|
|
1026
1041
|
spin.add_key('pick_size', 's - #rows in pick', vals=[1, 2, 3])
|
|
1027
1042
|
spin.add_key('name', 'n - select name', prompt='Provide Your Name:')
|
|
1028
1043
|
spin.add_key('mult', 'm - row multiplier', vals=[0.5, 0.9, 1.0, 1.1, 2, 4, 16])
|
|
1044
|
+
spin.add_key('quit', 'q,CTL-C - quit the app', category='action', keys={0x3, ord('q')})
|
|
1029
1045
|
opts = spin.default_obj
|
|
1030
|
-
other_keys = {0x3, ord('q')}
|
|
1031
1046
|
|
|
1032
|
-
win = ConsoleWindow(head_line=True, keys=spin.keys
|
|
1047
|
+
win = ConsoleWindow(head_line=True, keys=spin.keys,
|
|
1033
1048
|
ctrl_c_terminates=False, body_rows=4000)
|
|
1034
1049
|
opts.name = ""
|
|
1035
1050
|
opts.prev_pick = 'n/a'
|
|
@@ -1041,12 +1056,10 @@ if __name__ == '__main__':
|
|
|
1041
1056
|
win.set_pick_mode(False)
|
|
1042
1057
|
spin.show_help_nav_keys(win)
|
|
1043
1058
|
spin.show_help_body(win)
|
|
1044
|
-
win.put_body('Other Keys:', curses.A_UNDERLINE)
|
|
1045
|
-
win.put_body(' q, CTRL-C: quit program')
|
|
1046
1059
|
else:
|
|
1047
1060
|
win.set_pick_mode(opts.pick_mode, opts.pick_size)
|
|
1048
1061
|
win.add_header(f'{time.monotonic():.3f} [p]ick={opts.pick_mode}'
|
|
1049
|
-
|
|
1062
|
+
+ f' s:#rowsInPick={opts.pick_size} [n]ame [m]ult={opts.pick_size} ?=help [q]uit')
|
|
1050
1063
|
win.add_header(f'Header: {loop} name="{opts.name}" {opts.prev_pick=}')
|
|
1051
1064
|
pick_values = []
|
|
1052
1065
|
for idx, line in enumerate(range(body_size//opts.pick_size)):
|
|
@@ -1068,4 +1081,4 @@ if __name__ == '__main__':
|
|
|
1068
1081
|
print("exception:", str(exce))
|
|
1069
1082
|
print(traceback.format_exc())
|
|
1070
1083
|
if dump_str:
|
|
1071
|
-
print(dump_str)
|
|
1084
|
+
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.3"
|
|
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
|