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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: console-window
3
- Version: 1.0.2
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 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
@@ -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
- win.add_body(f'{ns.descr:>{self.align}}: ')
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.vals:
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
- else:
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
- elif key in [curses.KEY_BACKSPACE, 127, 8]: # Backspace
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 key in (ord('q'), 0x3):
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 - oggle pick mode, turn off to pick current line', vals=[False, True])
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^other_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
- + f' s:#rowsInPick={opts.pick_size} [n]ame [m]ult={opts.pick_size} [q]uit')
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.2"
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