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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: console-window
3
- Version: 1.0.2
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 key in (ord('q'), ord('Q')):
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 key in (ord('q'), ord('Q')):
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, keys=None, comments=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
- ns.vals, ns.prompt = vals, prompt
190
- assert bool(ns.vals) ^ bool(ns.prompt) # Must be EITHER vals OR prompt
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
- win.add_body(f'{ns.descr:>{self.align}}: ')
231
+ colon = '' if ns.category == 'action' else ':'
232
+ win.add_body(f'{ns.descr:>{self.align}}{colon} ')
222
233
 
223
- for choice in choices:
224
- shown = f'{choice}'
225
- if isinstance(choice, bool):
226
- shown = "ON" if choice else "off"
227
- win.add_body(' ', resume=True)
228
- win.add_body(shown, resume=True,
229
- attr=curses.A_REVERSE if choice == value else None)
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.vals:
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
- else:
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
- elif key in [curses.KEY_BACKSPACE, 127, 8]: # Backspace
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 key in (ord('q'), 0x3):
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 - oggle pick mode, turn off to pick current line', vals=[False, True])
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^other_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
- + f' s:#rowsInPick={opts.pick_size} [n]ame [m]ult={opts.pick_size} [q]uit')
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.2"
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