[tor-commits] [nyx/master] Curses str_input() function for getting input
atagar at torproject.org
atagar at torproject.org
Mon Apr 18 20:23:16 UTC 2016
commit da970b76736b836360da85de1b609d584990d002
Author: Damian Johnson <atagar at torproject.org>
Date: Sun Apr 17 19:50:31 2016 -0700
Curses str_input() function for getting input
Replacing our Panel's getstr() method with simplified version in our curses
module. This drops workarounds to work with python 2.5 (meh) and formatted
initial text (unused).
---
nyx/curses.py | 66 ++++++++++++++++++++++++
nyx/panel/__init__.py | 139 --------------------------------------------------
nyx/panel/header.py | 4 +-
3 files changed, 68 insertions(+), 141 deletions(-)
diff --git a/nyx/curses.py b/nyx/curses.py
index ebe5eef..f357443 100644
--- a/nyx/curses.py
+++ b/nyx/curses.py
@@ -14,6 +14,7 @@ if we want Windows support in the future too.
start - initializes curses with the given function
raw_screen - provides direct access to the curses screen
key_input - get keypress by user
+ str_input - text field where user can input a string
curses_attr - curses encoded text attribute
screen_size - provides the dimensions of our screen
screenshot - dump of the present on-screen content
@@ -82,6 +83,8 @@ from __future__ import absolute_import
import collections
import curses
+import curses.ascii
+import curses.textpad
import threading
import stem.util.conf
@@ -239,6 +242,69 @@ def key_input(input_timeout = None):
return KeyInput(CURSES_SCREEN.getch())
+def str_input(x, y, initial_text = ''):
+ """
+ Provides a text field where the user can input a string, blocking until
+ they've done so and returning the result. If the user presses escape then
+ this terminates and provides back **None**.
+
+ This blanks any content within the space that the input field is rendered
+ (otherwise stray characters would be interpreted as part of the initial
+ input).
+
+ :param int x: horizontal location
+ :param int y: vertical location
+ :param str initial_text: initial input of the field
+
+ :returns: **str** with the user input or **None** if the prompt is caneled
+ """
+
+ def handle_key(textbox, key):
+ y, x = textbox.win.getyx()
+
+ if key == 27:
+ return curses.ascii.BEL # user pressed esc
+ elif key == curses.KEY_HOME:
+ textbox.win.move(y, 0)
+ elif key in (curses.KEY_END, curses.KEY_RIGHT):
+ msg_length = len(textbox.gather())
+ textbox.win.move(y, x) # reverts cursor movement during gather call
+
+ if key == curses.KEY_END and msg_length > 0 and x < msg_length - 1:
+ textbox.win.move(y, msg_length - 1) # if we're in the content then move to the end
+ elif key == curses.KEY_RIGHT and x < msg_length - 1:
+ textbox.win.move(y, x + 1) # only move cursor if there's content after it
+ elif key == 410:
+ # if we're resizing the display during text entry then cancel it
+ # (otherwise the input field is filled with nonprintable characters)
+
+ return curses.ascii.BEL
+ else:
+ return key
+
+ with CURSES_LOCK:
+ try:
+ curses.curs_set(1) # show cursor
+ except curses.error:
+ pass
+
+ width = screen_size().width - x
+
+ curses_subwindow = CURSES_SCREEN.subwin(1, width, y, x)
+ curses_subwindow.erase()
+ curses_subwindow.addstr(0, 0, initial_text[:width - 1])
+
+ textbox = curses.textpad.Textbox(curses_subwindow, insert_mode = True)
+ user_input = textbox.edit(lambda key: handle_key(textbox, key)).strip()
+
+ try:
+ curses.curs_set(0) # hide cursor
+ except curses.error:
+ pass
+
+ return None if textbox.lastcmd == curses.ascii.BEL else user_input
+
+
def curses_attr(*attributes):
"""
Provides encoding for the given curses text attributes.
diff --git a/nyx/panel/__init__.py b/nyx/panel/__init__.py
index 39b8f85..1f5143d 100644
--- a/nyx/panel/__init__.py
+++ b/nyx/panel/__init__.py
@@ -7,8 +7,6 @@ Panels consisting the nyx interface.
import collections
import curses
-import curses.ascii
-import curses.textpad
import inspect
import threading
import time
@@ -19,8 +17,6 @@ import stem.util.log
from nyx.curses import HIGHLIGHT
from stem.util import conf, str_tools
-PASS = -1
-
__all__ = [
'config',
'connection',
@@ -82,76 +78,6 @@ class KeyHandler(collections.namedtuple('Help', ['key', 'description', 'current'
self._action()
-class BasicValidator(object):
- """
- Interceptor for keystrokes given to a textbox, doing the following:
- - quits by setting the input to curses.ascii.BEL when escape is pressed
- - stops the cursor at the end of the box's content when pressing the right
- arrow
- - home and end keys move to the start/end of the line
- """
-
- def validate(self, key, textbox):
- """
- Processes the given key input for the textbox. This may modify the
- textbox's content, cursor position, etc depending on the functionality
- of the validator. This returns the key that the textbox should interpret,
- PASS if this validator doesn't want to take any action.
-
- Arguments:
- key - key code input from the user
- textbox - curses Textbox instance the input came from
- """
-
- result = self.handle_key(key, textbox)
- return key if result == PASS else result
-
- def handle_key(self, key, textbox):
- y, x = textbox.win.getyx()
-
- if curses.ascii.isprint(key) and x < textbox.maxx:
- # Shifts the existing text forward so input is an insert method rather
- # than replacement. The curses.textpad accepts an insert mode flag but
- # this has a couple issues...
- # - The flag is only available for Python 2.6+, before that the
- # constructor only accepted a subwindow argument as per:
- # https://trac.torproject.org/projects/tor/ticket/2354
- # - The textpad doesn't shift text that has text attributes. This is
- # because keycodes read by textbox.win.inch() includes formatting,
- # causing the curses.ascii.isprint() check it does to fail.
-
- current_input = textbox.gather()
- textbox.win.addstr(y, x + 1, current_input[x:textbox.maxx - 1])
- textbox.win.move(y, x) # reverts cursor movement during gather call
- elif key == 27:
- # curses.ascii.BEL is a character codes that causes textpad to terminate
-
- return curses.ascii.BEL
- elif key == curses.KEY_HOME:
- textbox.win.move(y, 0)
- return None
- elif key in (curses.KEY_END, curses.KEY_RIGHT):
- msg_length = len(textbox.gather())
- textbox.win.move(y, x) # reverts cursor movement during gather call
-
- if key == curses.KEY_END and msg_length > 0 and x < msg_length - 1:
- # if we're in the content then move to the end
-
- textbox.win.move(y, msg_length - 1)
- return None
- elif key == curses.KEY_RIGHT and x >= msg_length - 1:
- # don't move the cursor if there's no content after it
-
- return None
- elif key == 410:
- # if we're resizing the display during text entry then cancel it
- # (otherwise the input field is filled with nonprintable characters)
-
- return curses.ascii.BEL
-
- return PASS
-
-
class Panel(object):
"""
Wrapper for curses subwindows. This hides most of the ugliness in common
@@ -504,71 +430,6 @@ class Panel(object):
return x, y
- def getstr(self, y, x, initial_text = ''):
- """
- Provides a text field where the user can input a string, blocking until
- they've done so and returning the result. If the user presses escape then
- this terminates and provides back None. This should only be called from
- the context of a panel's draw method.
-
- This blanks any content within the space that the input field is rendered
- (otherwise stray characters would be interpreted as part of the initial
- input).
-
- Arguments:
- y - vertical location
- x - horizontal location
- initial_text - starting text in this field
- """
-
- # makes cursor visible
-
- try:
- previous_cursor_state = curses.curs_set(1)
- except curses.error:
- previous_cursor_state = 0
-
- # temporary subwindow for user input
-
- display_width = self.get_preferred_size()[1]
-
- with nyx.curses.raw_screen() as stdscr:
- input_subwindow = stdscr.subwin(1, display_width - x, self.top + y, self.left + x)
-
- # blanks the field's area, filling it with the font in case it's hilighting
-
- input_subwindow.clear()
- input_subwindow.bkgd(' ', curses.A_NORMAL)
-
- # prepopulates the initial text
-
- if initial_text:
- input_subwindow.addstr(0, 0, initial_text[:display_width - x - 1], curses.A_NORMAL)
-
- # Displays the text field, blocking until the user's done. This closes the
- # text panel and returns user_input to the initial text if the user presses
- # escape.
-
- textbox = curses.textpad.Textbox(input_subwindow)
-
- validator = BasicValidator()
-
- textbox.win.attron(curses.A_NORMAL)
- user_input = textbox.edit(lambda key: validator.validate(key, textbox)).strip()
- textbox.win.attroff(curses.A_NORMAL)
-
- if textbox.lastcmd == curses.ascii.BEL:
- user_input = None
-
- # reverts visability settings
-
- try:
- curses.curs_set(previous_cursor_state)
- except curses.error:
- pass
-
- return user_input
-
def add_scroll_bar(self, top, bottom, size, draw_top = 0):
"""
Draws a left justified scroll bar reflecting position within a vertical
diff --git a/nyx/panel/header.py b/nyx/panel/header.py
index 2441704..6551800 100644
--- a/nyx/panel/header.py
+++ b/nyx/panel/header.py
@@ -47,7 +47,7 @@ class HeaderPanel(nyx.panel.DaemonPanel):
nyx.panel.DaemonPanel.__init__(self, 'header', UPDATE_RATE)
self._vals = Sampling.create()
- self._last_width = nyx.curses.screen_size()[0]
+ self._last_width = nyx.curses.screen_size().width
self._reported_inactive = False
self._message = None
@@ -90,7 +90,7 @@ class HeaderPanel(nyx.panel.DaemonPanel):
self.show_message(message)
self.redraw(True)
- user_input = self.getstr(self.get_height() - 1, len(message), initial_value)
+ user_input = nyx.curses.str_input(len(message), self.get_height() - 1, initial_value)
self.show_message()
return user_input
More information about the tor-commits
mailing list