def create_textbox(self, color_code, default_value=None, validate=None): is_valid = False data = None while(not is_valid): self._window.hline(self.y + 1, self.x, "_", 16) self._window.refresh() new_win = curses.newwin(self.h, self.w, self.y + self.header_height, self.x) text = Textbox(new_win, insert_mode=False) if default_value: for i in default_value: text.do_command(ord(i)) data = text.edit() if not validate: is_valid = True elif getattr(Validation, validate)(data.strip()): is_valid = True else: col_code_attr = ColorCode().get_color_pair(3) self._obj.on_attr(col_code_attr) self._window.addstr(self.header_height - 1, 3, f"Error: Invalid {validate}" f" {data.strip()} Please re-enter") self._obj.off_attr(col_code_attr) self._window.refresh() return data
class MiniWindow: def __init__(self): self.__mini_window = curses.newwin(1, curses.COLS, curses.LINES - 1, 0) self.__mini_window.keypad(True) self.__mini_window_tb = Textbox(self.__mini_window) def display_message_in_mini_buffer(self, msg): self.__mini_window.erase() self.__mini_window.addstr(msg) self.__mini_window.refresh() def get_file_name(self): self.display_message_in_mini_buffer('file to save in: ') filename = "" while True: mini_buffer_ch = self.__mini_window.getch() if mini_buffer_ch == 24: self.__mini_window.erase() self.__mini_window.refresh() return None elif mini_buffer_ch == 10: return filename else: self.__mini_window_tb.do_command(mini_buffer_ch) filename += chr(mini_buffer_ch)
def edited_text(scr, text, y, x, w=50, prompt="Edit the text then Ctrl-G to exit"): """ Provides the editing capability: Returns the edited (or not) version of text. Editing window begins at (y,x) of the <scr>een, consists of 3 rows and is w+2 characters wide. Text to be edited appears on line y+1 beginning in column x+1 within a 'bordered' window with room for w characters or as many as are in text, which ever is the greater. The <prompt> overwrites the box border in bold. """ # scr. # scr.refresh() if l := len(text) > w: w = l # create the text box with border around the outside tb_border = cur.newwin(3, w + 2, y, x) tb_border.box() # place promt on line above the box tb_border.refresh() scr.addstr(y, x + 2, prompt, cur.A_BOLD) scr.refresh() tb_body = cur.newwin(1, w, y + 1, x + 1) tb = Textbox(tb_body) for ch in text: # insert starting text tb.do_command(ch) tb.edit() # start the editor running, Ctrl-G ends s2 = tb.gather() # fetch the contents scr.clear() # clear the screen # return s2 return s2.strip()
def __init__(self, win, insert_mode=False, text=''): Textbox.__init__(self, win, insert_mode) for chr in text: if chr == '\n': Textbox.do_command(self, curses.ascii.NL) else: Textbox.do_command(self, chr)
def edit_box(): global box, NUM_LINES, NUM_COLS, TOP, LEFT, chosen_option, edit_box_message editwin = curses.newwin(NUM_LINES, NUM_COLS - 1, TOP, LEFT + len(chosen_option) + 2) box = Textbox(editwin) for char in edit_box_message: box.do_command(char) box.edit(navigator)
def do_command(self, ch): if ch == 127: # backspace Textbox.do_command(self, curses.KEY_BACKSPACE) self.win.refresh() return 1 if ch == 27: Textbox.gather(self) return -1 return Textbox.do_command(self, ch)
def new_game_get_seed(win): # Ask for a random seed to start the game win.clear() win.addstr(0, 0, SEED_STRING) # create a text box for the user to type in editwin = curses.newpad(1, 32) rectangle(win, 5, 0, 1 + 5 + 1, 1 + 32 + 1) win_height = curses.LINES - 2 win_width = curses.COLS - 4 win.refresh(0, 0, 1, 2, win_height, win_width) box = Textbox(editwin) while True: # wait for user input win.refresh(0, 0, 1, 2, win_height, win_width) editwin.refresh(0, 0, 7, 4, 7 + 1, 4 + 32) c = editwin.getch() # handle special cases: if c == 10: # [enter] break elif c == curses.KEY_RESIZE: # window resizing stdscr.clear() stdscr.border('|', '|', '-', '-', '+', '+', '+', '+') stdscr.refresh() rectangle(win, 5, 0, 1 + 5 + 1, 1 + 32 + 1) h, w = stdscr.getmaxyx() assert h > 2 and w > 10 win_height = h - 2 win_width = w - 4 win.resize(100, win_width - 1) win.refresh(0, 0, 1, 2, win_height, win_width) # draw the user input in the text box box.do_command(c) # Get resulting contents message = box.gather() win.clear() if message: logger.info(f"Seed by user: {message}") return message else: seed = random.randint(1, sys.maxsize) logger.info(f"Seed generated: {seed}") return seed
def do_command(self, ch): if ch == curses.KEY_RESIZE: raise errors.CursesScreenResizedError() if ch == 10: # Enter return 0 if ch == 127: # BackSpace return 8 return Textbox.do_command(self, ch)
def do_command(self, ch): if ch == curses.KEY_RESIZE: resizeWindows() for i in range(8): # delete 8 input window lines Textbox.do_command(self, 1) Textbox.do_command(self, 11) Textbox.do_command(self, 16) return Textbox.do_command(self, 7) if ch == 10: # Enter return 0 if ch == 127: # Backspace return 8 return Textbox.do_command(self, ch)
def main4(screen): curses.curs_set(False) cursor_pos = 0 screen.addstr(1, 1, "Enter a text or something yo") height, width, y, x = 1, 20, 4, 1 editwin = curses.newwin(height, width, y, x) curses.curs_set(True) rectangle(screen, y - 1, x - 1, height + y, width + x) screen.refresh() box = Textbox(editwin) for c in 'test input': box.do_command(c) box.edit(enter_is_terminate) message = box.gather() #screen.refresh() #screen.move(y,x) editwin = curses.newwin(height, width, y, x) curses.curs_set(True) rectangle(screen, y - 1, x - 1, height + y, width + x) screen.refresh() box = Textbox(editwin) for c in message: box.do_command(c) box.edit(enter_is_terminate) curses.curs_set(False)
def do_command(self, ch: int) -> int: """ Change the default textbox behavior of up, and down. Â Â Â Â """ if ch == curses.KEY_UP: return 1 elif ch == curses.KEY_DOWN: return 1 else: return Textbox.do_command(self, ch)
class EditField(ScrollWindow): '''EditFields represent editable text areas on the screen At any time, the text of the object can be accessed by referencing an EditField's 'text' parameter (edit_field.text). Note that this returns a list of character codes. To get a string, see EditField.get_text() ''' ASTERISK_CHAR = ord('*') LARROW_CHAR = ord('<') CMD_DONE_EDIT = ord(ctrl('g')) CMD_MV_BOL = ord(ctrl('a')) # Move to beginning of line CMD_MV_EOL = ord(ctrl('e')) # Move to end of line CMD_CLR_LINE = ord(ctrl('k')) def __init__(self, area, window=None, text="", masked=False, color_theme=None, color=None, highlight_color=None, validate=None, on_exit=None, error_win=None, numeric_pad=None, **kwargs): '''See InnerWindow.__init__ In general, specifying a specific color_theme is unnecessary, unless this instance requires a theme other than that of the rest of the program window (required): A parent InnerWindow area (required): Describes the area within the parent window to be used. area.lines must be 1, as only single line EditFields are currently supported. text (optional): The text in this field, before it is edited by the user. Defaults to empty string. masked (optional): If True, then this field will display bullets when the user types into it, instead of echo'ing the text color_theme (optional): The color_theme for this edit field. By default the parent window's theme is used. color_theme.edit_field is used as the background color; color_theme.highlight_edit is the background when this EditField is active. validate (optional) - A function for validation on each keystroke. The function passed in should take as parameter a string, and if invalid, it should raise a UIMessage with an indicator of why. This function, if present, will be called after each keystroke. IMPORTANT: If no UIMessage is raised, the string is assumed to be valid Additional keyword arguments can be passed to this function by modifying this EditField's validate_kwargs dictionary on_exit (optional) - A function for final validation. This is called when this EditField loses focus. Like validate, it should accept a string argument and raise a UIMessage if the string is not valid. Additional keyword arguments can be passed to this function by modifying this EditField's on_exit_kwargs dictionary error_win (optional) - If given, error_win.display_error() is called whenever validate or on_exit raise a UIMessage (the UIMessage's explanation string is passed as parameter). Additionally, error_win.clear_err() is called when those functions return successfully. numeric_pad (optional) - A single character to pad self.text with. If given, when this EditField handles input, it works in numeric mode. In numeric mode: * When editing begins, numeric_pad is stripped from the current text in the field. Text is shifted to the left to compensate * When done editing, the text is right justified, and padded with the value of numeric_pad. In general, either a space or zero will be used as the value of numeric_pad * IMPORTANT: The function hooks for 'validate' and 'on_exit' are called using the value of self.get_text PRIOR to padding. ''' self._modified = False self.numeric_pad = numeric_pad self.right_justify = False self.on_exit_kwargs = {} self.validate_kwargs = {} self.validate = validate self.on_exit = on_exit self.error_win = error_win if color_theme is None: color_theme = window.color_theme if color is None: color = color_theme.edit_field if highlight_color is None: highlight_color = color_theme.highlight_edit if area.lines != 1: raise ValueError("area.lines must be 1") super(EditField, self).__init__(area, window=window, color_theme=color_theme, color=color, highlight_color=highlight_color, **kwargs) self.masked = masked self.masked_char = EditField.ASTERISK_CHAR self.textbox = Textbox(self.window) self.textbox.stripspaces = True self.input_key = None self.text = None self.key_dict[curses.KEY_ENTER] = consume_action self.key_dict[curses.KEY_LEFT] = no_action self.key_dict[curses.KEY_RIGHT] = no_action # Set use_horiz_scroll_bar to False to let edit_field handle # the drawing of the horiz scroll arrow, since it is inline with # the text. Do this before calling set_text, which does a screen # update and use_horiz_scroll_bar needs to be False by then. self.use_horiz_scroll_bar = False self._set_text(text) self.clear_on_enter = False @property def modified(self): '''Returns True if the text in this field has been modified since the field was created''' return self._modified def set_text(self, text): '''Set the text of this EditField to text. Processes each character individually; thus emulating a user typing in the text. This means that each substring of text that start at text[0] must be valid in the context of any validation function this EditField has. If numeric_pad is set, pad text with it if field isn't blank. ''' self._modified = True self._set_text(text) def _do_pad(self, text): '''Apply the numeric padding to text''' if self.numeric_pad is not None: width = self.area.columns - 1 if text: text = text.lstrip(self.numeric_pad) text = rjust_columns(text, width, self.numeric_pad) return text def _set_text(self, text, do_pad=True): '''Used internally to bypass the the public interface's numeric_pad functionality''' if do_pad: text = self._do_pad(text) if text is None: text = u"" self._reset_text() terminalui.LOGGER.log(LOG_LEVEL_INPUT, "_set_text textwidth=%s, columns=%s, text=%s", textwidth(text), self.area.columns, text) length_diff = textwidth(text) - self.area.columns if (length_diff >= 0): self.scroll(scroll_to_column=length_diff) for idx, char in enumerate(text): if length_diff > 0 and idx == length_diff: self.textbox.do_command(EditField.LARROW_CHAR) elif self.masked: self.textbox.do_command(self.masked_char) else: self.textbox.do_command(ord(char)) self.text.append(char) self.no_ut_refresh() def handle_input(self, input_key): ''' For each keystroke, determine if it's a special character (and needs to end editing), printable character, or backspace. For special characters, send the return the done-editing code (CTRL-G), and store the special character for processing by parent window objects. If printable, append it to self.text, and try to validate. If validation fails, reject the character. ''' input_key = self.translate_input(input_key) if self.is_special_char(input_key): self.input_key = input_key return EditField.CMD_DONE_EDIT else: self.input_key = None if isprint(input_key) or (ismeta(input_key) and input_key < curses.KEY_MIN): # isprint: ASCII characters # ismeta and < curses.KEY_MIN: Remaining UTF-8 characters # > curses.KEY_MIN: Special key such as down arrow, backspace, etc. self.text.append(unichr(input_key)) if not self.is_valid(): if len(self.text) > 0: self.text.pop() return None self._modified = True if self.masked: input_key = self.masked_char elif input_key == curses.KEY_BACKSPACE: if len(self.text) > 0: del_char = self.text.pop() self._modified = True del_width = charwidth(del_char) if textwidth(self.get_text()) >= self.area.columns: self.scroll(columns=-del_width) self.is_valid() # Run self.is_valid here so that any functional side effects can # occur, but don't check the return value (removing a character # from a valid string should never be invalid, and even if it were, # it would not make sense to add the deleted character back in) return input_key def edit_loop(self): '''Loop for handling characters in an edit field. Called by EditField.process(). ''' input_key = None while input_key != EditField.CMD_DONE_EDIT: input_key = self.handle_input(self.getch()) self._set_text(self.get_text()) curses.doupdate() def process(self, input_key): '''Process a keystroke. For an EditField, this means preparing the textpad for processing and passing the input_key in. Try to enable the blinking cursor (if it was disabled) before editing begins, so the user can see where they're typing. Once finished, restore the cursor state to its previous condition. After editing, return self.input_key, which will either be None (indicating all keystrokes were processed by the Textbox) or a special character (such as F2) which caused EditField.handle_input to stop processing text through the Textbox. ''' try: curses.curs_set(2) except curses.error: terminalui.LOGGER.debug("Got curses.error when enabling cursor") if input_key is not None and not self.is_special_char(input_key): # Put input_key back on stack so that textbox.edit can read it curses.ungetch(input_key) if self.numeric_pad is not None: self._set_text(self.get_text().lstrip(self.numeric_pad), do_pad=False) if self.clear_on_enter: self.clear_text() else: # Move to end of previous input. self.textbox.do_command(EditField.CMD_MV_EOL) self.edit_loop() return_key = self.input_key if self.numeric_pad is not None: self._set_text(self.get_text()) else: return_key = input_key terminalui.LOGGER.debug("Returning: %s", return_key) return return_key def get_text(self): '''Join the array of characters as a unicode string''' return u"".join(self.text) def is_special_char(self, input_key): '''Check to see if this is a keystroke that should break us out of adding input to the Textbox and return control to the parent window ''' if (input_key in range(curses.KEY_F0, curses.KEY_F10) or input_key in self.key_dict): return True else: return False def is_valid(self): '''Check to see if the text we have is valid or not. First check the length to make sure it fits in the space alloted. If it doesn't, flag an error indicating the maximum supported length for the field. Then, if this EditField has a validate function, call it (passing in any validate_kwargs we have). If validate raises an exception, display the error (if we have a handle to an ErrorWindow) and return False. ''' win_size_x = self.window.getmaxyx()[1] if len(self.get_text().lstrip(self.numeric_pad)) >= win_size_x: if self.error_win is not None: self.error_win.display_err(_("Max supported field length: %s") % (win_size_x - 1)) return False elif callable(self.validate): try: self.validate(self, **self.validate_kwargs) except UIMessage as error_str: if self.error_win is not None: self.error_win.display_err(unicode(error_str)) return False if self.error_win is not None and self.error_win.visible: self.error_win.clear_err() return True def run_on_exit(self): '''Fire the on_exit function, if there is one. If an error occurs, and this EditField has an error_win, display it there. ''' if callable(self.on_exit): try: self.on_exit(self, **self.on_exit_kwargs) if self.error_win is not None and self.error_win.visible: self.error_win.clear_err() return True except UIMessage as error_str: if self.error_win is not None: self.error_win.display_err(unicode(error_str)) return False return True def make_active(self): '''Enable the cursor when activating this field''' super(EditField, self).make_active() try: curses.curs_set(2) except curses.error: terminalui.LOGGER.debug("Got curses.error when enabling cursor") def make_inactive(self): '''Fire the on_exit function before leaving the field and making it inactive. ''' self.run_on_exit() try: curses.curs_set(0) except curses.error: terminalui.LOGGER.debug("Got curses.error when reverting cursor") super(EditField, self).make_inactive() def _reset_text(self): '''Resets the text area, either to permanently remove the text, or as part of the process of redrawing with additional text (during self._set_text()) ''' # Move cursor to left side of window self.textbox.do_command(EditField.CMD_MV_BOL) # Clear from cursor to end of line self.textbox.do_command(EditField.CMD_CLR_LINE) self.text = [] self.no_ut_refresh() def clear_text(self): '''Issue the commands to textbox to clear itself, reset self.text to an empty array, and reset horizontal scrolling. ''' self._modified = True self.scroll(scroll_to_column=0) self._reset_text() def get_cursor_loc(self): '''Cursor should be positioned at the end of the entered text''' win_loc = self.window.getbegyx() x_loc = win_loc[1] if not self.clear_on_enter: x_loc += min(textwidth(self.get_text()), self.area.columns) return (win_loc[0], x_loc)
class cursesWindow(): __shared_state = {} def __init__(self, pad=None): self.__dict__ = self.__shared_state if not pad: pad = cursesPad() self.pad = pad self.screen = '' self.window = '' self.textbox = '' self.size = os.popen('stty size', 'r').read().split() def initialize(self): self.initializeWindow() self.initializeColorScheme() def initializeWindow(self): self.screen = curses.initscr() curses.start_color() curses.use_default_colors() curses.noecho() curses.cbreak() self.screen.keypad(1) self.setCursorVisibility(0) self.window = curses.newwin(int(self.size[0]), int(self.size[1]), 0, 0) signal.signal(signal.SIGWINCH, self.windowSizeChangeEventHandler) def initializeColorScheme(self): curses.init_pair(2, curses.COLOR_RED, -1) curses.init_pair(3, curses.COLOR_GREEN, -1) curses.init_pair(4, curses.COLOR_YELLOW, -1) curses.init_pair(5, curses.COLOR_BLUE, -1) curses.init_pair(6, curses.COLOR_MAGENTA, -1) curses.init_pair(7, curses.COLOR_CYAN, -1) curses.init_pair(8, curses.COLOR_WHITE, -1) def windowSizeChangeEventHandler(self, n, frame): self.size = os.popen('stty size', 'r').read().split() def setCursorVisibility(self, visible): try: curses.curs_set(visible) except: pass def getUserInput(self): return self.screen.getch() def close(self): self.setCursorVisibility(1) curses.nocbreak() self.screen.keypad(0) curses.echo() curses.endwin() def getInput(self, edit_window): self.setCursorVisibility(1) value = curses.textpad.Textbox(edit_window, insert_mode=True).edit().strip() self.setCursorVisibility(0) return value def wait(self, delay): self.screen.timeout(delay) def refresh(self): try: self.screen.noutrefresh() except: pass def update(self): curses.doupdate() def clear(self): self.screen.clear() self.pad.clear() # Drawing def addStr(self, string, color=''): try: if not color: self.screen.addstr(string) else: self.screen.addstr(string, curses.color_pair(color)) except: pass def addString(self, posX, posY, string, color=''): try: if not color: self.screen.addstr(posY, posX, string) else: self.screen.addstr(posY, posX, string, curses.color_pair(color)) except: pass def draw(self, screen): if not screen: return if len(screen[0]) == 4: for (y, x, line, color) in screen: self.addString(y, x, line, color) elif len(screen[0]) == 2: for (line, color) in screen: self.addStr(line, color) def editTextOnScreen(self, line_index, line_contents): editwin = self.window.subwin(1, int(self.size[1]) - 33, line_index, 7) editwin.addstr(0, 0, ' ' * (int(self.size[1]) - 34)) editwin.addstr(0, 0, line_contents) return self.editTextField(editwin) def editTextField(self, editwin): self.setCursorVisibility(1) self.textbox = Textbox(editwin, insert_mode=True) newname = self.textbox.edit(self.deleteKeyPressHandler).strip() self.textbox = '' self.setCursorVisibility(0) return newname def deleteKeyPressHandler(self, keyPressed): if keyPressed in (330, 263, 127): # Delete self.textbox.do_command(curses.KEY_BACKSPACE) else: return keyPressed return
def do_command(self, ch): if ch == 10: # Enter return 0 if ch == 127: # Enter return 8 return Textbox.do_command(self, ch)
class NewTextbox: height, width = 2, 20 y, x = 4, 1 text = "" _add_row = False _screen = None _curses_window = None _textbox = None def __init__(self, height, width, y, x, text="", screen=None): self.height, self.width, self.y, self.x = height, width, y, x self.text = text self._screen = screen #self._curses_window = curses.newwin(height, width, y, x) #self._textbox = Textbox(self._curses_window) self.redraw() def draw_rectangle(self): if self._screen is None: return rectangle(self._screen, self.y - 1, self.x - 1, self.height + self.y, self.width + self.x) self._screen.refresh() def redraw(self): self._curses_window = curses.newwin(self.height, self.width, self.y, self.x) self._textbox = Textbox(self._curses_window) self._textbox.stripspaces = False def edit(self, text=""): if self._screen is None: #print("screen needs to be set") return exit = False i = 0 while exit == False: self.redraw() for c in self.text: self._textbox.do_command(c) #if self._add_row == True: # self._textbox.do_command(chr(16)) # self._add_row = False self.draw_rectangle() self._textbox.edit(self.enter_is_terminate) self.text = self._textbox.gather() if self._add_row == True: self.add_row() i = i + 1 if i == 3: exit = True def enter_is_terminate(self, c): if c == 10: self.add_row = True self.height = self.height + 1 return 7 return c def add_row(self): self.height = self.height + 1
class EditField(ScrollWindow): '''EditFields represent editable text areas on the screen At any time, the text of the object can be accessed by referencing an EditField's 'text' parameter (edit_field.text). Note that this returns a list of character codes. To get a string, see EditField.get_text() ''' ASTERISK_CHAR = ord('*') LARROW_CHAR = ord('<') CMD_DONE_EDIT = ord(ctrl('g')) CMD_MV_BOL = ord(ctrl('a')) # Move to beginning of line CMD_MV_EOL = ord(ctrl('e')) # Move to end of line CMD_CLR_LINE = ord(ctrl('k')) def __init__(self, area, window=None, text="", masked=False, color_theme=None, color=None, highlight_color=None, validate=None, on_exit=None, error_win=None, numeric_pad=None, **kwargs): '''See InnerWindow.__init__ In general, specifying a specific color_theme is unnecessary, unless this instance requires a theme other than that of the rest of the program window (required): A parent InnerWindow area (required): Describes the area within the parent window to be used. area.lines must be 1, as only single line EditFields are currently supported. text (optional): The text in this field, before it is edited by the user. Defaults to empty string. masked (optional): If True, then this field will display bullets when the user types into it, instead of echo'ing the text color_theme (optional): The color_theme for this edit field. By default the parent window's theme is used. color_theme.edit_field is used as the background color; color_theme.highlight_edit is the background when this EditField is active. validate (optional) - A function for validation on each keystroke. The function passed in should take as parameter a string, and if invalid, it should raise a UIMessage with an indicator of why. This function, if present, will be called after each keystroke. IMPORTANT: If no UIMessage is raised, the string is assumed to be valid Additional keyword arguments can be passed to this function by modifying this EditField's validate_kwargs dictionary on_exit (optional) - A function for final validation. This is called when this EditField loses focus. Like validate, it should accept a string argument and raise a UIMessage if the string is not valid. Additional keyword arguments can be passed to this function by modifying this EditField's on_exit_kwargs dictionary error_win (optional) - If given, error_win.display_error() is called whenever validate or on_exit raise a UIMessage (the UIMessage's explanation string is passed as parameter). Additionally, error_win.clear_err() is called when those functions return successfully. numeric_pad (optional) - A single character to pad self.text with. If given, when this EditField handles input, it works in numeric mode. In numeric mode: * When editing begins, numeric_pad is stripped from the current text in the field. Text is shifted to the left to compensate * When done editing, the text is right justified, and padded with the value of numeric_pad. In general, either a space or zero will be used as the value of numeric_pad * IMPORTANT: The function hooks for 'validate' and 'on_exit' are called using the value of self.get_text PRIOR to padding. ''' self._modified = False self.numeric_pad = numeric_pad self.right_justify = False self.on_exit_kwargs = {} self.validate_kwargs = {} self.validate = validate self.on_exit = on_exit self.error_win = error_win if color_theme is None: color_theme = window.color_theme if color is None: color = color_theme.edit_field if highlight_color is None: highlight_color = color_theme.highlight_edit if area.lines != 1: raise ValueError("area.lines must be 1") super(EditField, self).__init__(area, window=window, color_theme=color_theme, color=color, highlight_color=highlight_color, **kwargs) self.masked = masked self.masked_char = EditField.ASTERISK_CHAR self.textbox = Textbox(self.window) self.textbox.stripspaces = True self.input_key = None self.text = None self.key_dict[curses.KEY_ENTER] = consume_action self.key_dict[curses.KEY_LEFT] = no_action self.key_dict[curses.KEY_RIGHT] = no_action # Set use_horiz_scroll_bar to False to let edit_field handle # the drawing of the horiz scroll arrow, since it is inline with # the text. Do this before calling set_text, which does a screen # update and use_horiz_scroll_bar needs to be False by then. self.use_horiz_scroll_bar = False self._set_text(text) self.clear_on_enter = False @property def modified(self): '''Returns True if the text in this field has been modified since the field was created''' return self._modified def set_text(self, text): '''Set the text of this EditField to text. Processes each character individually; thus emulating a user typing in the text. This means that each substring of text that start at text[0] must be valid in the context of any validation function this EditField has. If numeric_pad is set, pad text with it if field isn't blank. ''' self._modified = True self._set_text(text) def _do_pad(self, text): '''Apply the numeric padding to text''' if self.numeric_pad is not None: width = self.area.columns - 1 if text: text = text.lstrip(self.numeric_pad) text = rjust_columns(text, width, self.numeric_pad) return text def _set_text(self, text, do_pad=True): '''Used internally to bypass the the public interface's numeric_pad functionality''' if do_pad: text = self._do_pad(text) if text is None: text = u"" self._reset_text() terminalui.LOGGER.log(LOG_LEVEL_INPUT, "_set_text textwidth=%s, columns=%s, text=%s", textwidth(text), self.area.columns, text) length_diff = textwidth(text) - self.area.columns if (length_diff >= 0): self.scroll(scroll_to_column=length_diff) for idx, char in enumerate(text): if length_diff > 0 and idx == length_diff: self.textbox.do_command(EditField.LARROW_CHAR) elif self.masked: self.textbox.do_command(self.masked_char) else: self.textbox.do_command(ord(char)) self.text.append(char) self.no_ut_refresh() def handle_input(self, input_key): ''' For each keystroke, determine if it's a special character (and needs to end editing), printable character, or backspace. For special characters, send the return the done-editing code (CTRL-G), and store the special character for processing by parent window objects. If printable, append it to self.text, and try to validate. If validation fails, reject the character. ''' input_key = self.translate_input(input_key) if self.is_special_char(input_key): self.input_key = input_key return EditField.CMD_DONE_EDIT else: self.input_key = None if isprint(input_key) or (ismeta(input_key) and input_key < curses.KEY_MIN): # isprint: ASCII characters # ismeta and < curses.KEY_MIN: Remaining UTF-8 characters # > curses.KEY_MIN: Special key such as down arrow, backspace, etc. self.text.append(unichr(input_key)) if not self.is_valid(): if len(self.text) > 0: self.text.pop() return None self._modified = True if self.masked: input_key = self.masked_char elif input_key == curses.KEY_BACKSPACE: if len(self.text) > 0: del_char = self.text.pop() self._modified = True del_width = charwidth(del_char) if textwidth(self.get_text()) >= self.area.columns: self.scroll(columns=-del_width) self.is_valid() # Run self.is_valid here so that any functional side effects can # occur, but don't check the return value (removing a character # from a valid string should never be invalid, and even if it were, # it would not make sense to add the deleted character back in) return input_key def edit_loop(self): '''Loop for handling characters in an edit field. Called by EditField.process(). ''' input_key = None while input_key != EditField.CMD_DONE_EDIT: input_key = self.handle_input(self.getch()) self._set_text(self.get_text()) curses.doupdate() def process(self, input_key): '''Process a keystroke. For an EditField, this means preparing the textpad for processing and passing the input_key in. Try to enable the blinking cursor (if it was disabled) before editing begins, so the user can see where they're typing. Once finished, restore the cursor state to its previous condition. After editing, return self.input_key, which will either be None (indicating all keystrokes were processed by the Textbox) or a special character (such as F2) which caused EditField.handle_input to stop processing text through the Textbox. ''' try: curses.curs_set(2) except curses.error: terminalui.LOGGER.debug("Got curses.error when enabling cursor") if input_key is not None and not self.is_special_char(input_key): # Put input_key back on stack so that textbox.edit can read it curses.ungetch(input_key) if self.numeric_pad is not None: self._set_text(self.get_text().lstrip(self.numeric_pad), do_pad=False) if self.clear_on_enter: self.clear_text() else: # Move to end of previous input. self.textbox.do_command(EditField.CMD_MV_EOL) self.edit_loop() return_key = self.input_key if self.numeric_pad is not None: self._set_text(self.get_text()) else: return_key = input_key terminalui.LOGGER.debug("Returning: %s", return_key) return return_key def get_text(self): '''Join the array of characters as a unicode string''' return u"".join(self.text) def is_special_char(self, input_key): '''Check to see if this is a keystroke that should break us out of adding input to the Textbox and return control to the parent window ''' if (input_key in range(curses.KEY_F0, curses.KEY_F10) or input_key in self.key_dict): return True else: return False def is_valid(self): '''Check to see if the text we have is valid or not. First check the length to make sure it fits in the space alloted. If it doesn't, flag an error indicating the maximum supported length for the field. Then, if this EditField has a validate function, call it (passing in any validate_kwargs we have). If validate raises an exception, display the error (if we have a handle to an ErrorWindow) and return False. ''' win_size_x = self.window.getmaxyx()[1] if len(self.get_text().lstrip(self.numeric_pad)) >= win_size_x: if self.error_win is not None: self.error_win.display_err( _("Max supported field length: %s") % (win_size_x - 1)) return False elif callable(self.validate): try: self.validate(self, **self.validate_kwargs) except UIMessage as error_str: if self.error_win is not None: self.error_win.display_err(unicode(error_str)) return False if self.error_win is not None and self.error_win.visible: self.error_win.clear_err() return True def run_on_exit(self): '''Fire the on_exit function, if there is one. If an error occurs, and this EditField has an error_win, display it there. ''' if callable(self.on_exit): try: self.on_exit(self, **self.on_exit_kwargs) if self.error_win is not None and self.error_win.visible: self.error_win.clear_err() return True except UIMessage as error_str: if self.error_win is not None: self.error_win.display_err(unicode(error_str)) return False return True def make_active(self): '''Enable the cursor when activating this field''' super(EditField, self).make_active() try: curses.curs_set(2) except curses.error: terminalui.LOGGER.debug("Got curses.error when enabling cursor") def make_inactive(self): '''Fire the on_exit function before leaving the field and making it inactive. ''' self.run_on_exit() try: curses.curs_set(0) except curses.error: terminalui.LOGGER.debug("Got curses.error when reverting cursor") super(EditField, self).make_inactive() def _reset_text(self): '''Resets the text area, either to permanently remove the text, or as part of the process of redrawing with additional text (during self._set_text()) ''' # Move cursor to left side of window self.textbox.do_command(EditField.CMD_MV_BOL) # Clear from cursor to end of line self.textbox.do_command(EditField.CMD_CLR_LINE) self.text = [] self.no_ut_refresh() def clear_text(self): '''Issue the commands to textbox to clear itself, reset self.text to an empty array, and reset horizontal scrolling. ''' self._modified = True self.scroll(scroll_to_column=0) self._reset_text() def get_cursor_loc(self): '''Cursor should be positioned at the end of the entered text''' win_loc = self.window.getbegyx() x_loc = win_loc[1] if not self.clear_on_enter: x_loc += min(textwidth(self.get_text()), self.area.columns) return (win_loc[0], x_loc)
def do_delete_line(self): y, x = self._win.getyx() self._win.move(y, 0) Textbox.do_command(self, 11) del TextSave.text[y] log("delete")
def inter_loop(self): start_y, start_x = self.scrs['text'].getyx() pad = Textbox(self.scrs['text']) pad.stripspaces = True msg = ['Welcome to CBTM Client.','List of accepted commands(separate '+\ 'the commands and the arguments with ","):'] for k, v in self.commands.items(): msg.append('%s -- %s' % (k, v)) data = '' while True: self.show_response(msg) msg = [] #Get user input while True: ch = self.scrs['text'].getch() if ch in self.pr: self.scrs['text'].echochar(ch) else: if ch == ord('\n'): data = self.get_text(pad) break elif ch == 14: continue elif ch == 16: self.show_text(data) self.scrs['text'].move(1, 1 + len(data)) elif ch == 263: tmp = self.get_text(pad)[:-1] self.show_text(tmp) self.scrs['text'].move(1, 1 + len(tmp)) else: # self.show_text(str(ch)) pad.do_command(ch) y, x = self.scrs['text'].getyx() if x < start_x: self.text_reset() #Separate the text entered by the user on commas args = data.split(',') try: #Get the first argument (Code for command) code = args.pop(0) except IndexError: code = -1 #Interprets the command try: command = self.commands[code] except KeyError: msg.append('Code not recognized: ' + code) msg.append('Try:') for k, v in self.commands.items(): msg.append('%s -- %s' % (k, v)) continue if command == 'quit': break elif command == 'set_port': #Execute set port self.port = int(args[0]) msg.append('Port for CBTM communication set to %d' % self.port) continue elif command == 'set_host': #Execute set host self.host = str(args[0]) msg.append('CBTM host set to %s' % self.host) continue elif code in ['rl', 'us', 'su', 'sdu', 'rbu']: # Checks for number of arguments if len(args) != 1: msg.append('The requested command needs one argument.') continue elif code in ['ru']: # Checks for number of arguments if len(args) != 4: msg.append('The requested command needs 4 arguments.') continue msg.append('Message from CBTM:') try: # Send command to CBTM Server rp = self.send(self.structure(command, args)) msg.extend(rp.split('\n')) except socket.error: msg = [ 'No server active on port %d' % self.port, ]
class InputWindow(InnerWindow): '''InputWindow represent editable text areas on the screen At any time, the text of the object can be accessed by referencing an InputWindow's 'text' parameter (edit_field.text). Note that this returns a list of character codes. ''' def __init__(self, area, window = None, text = "", masked = False, color_theme = None, at_index = None): '''Unlike InnerWindow, this constructor WILL NOT TAKE curses.window objects for the window parameter - they must be InnerWindows. In general, specifying a specific color_theme is unnecessary, unless this instance requires a theme other than that of the rest of the program window (required): A parent InnerWindow area (required): Describes the area within the parent window to be used. area.lines is overridden to 1. text (optional): The text in this field, before it is edited by the user. Defaults to empty string. masked (optional): If True, then this field will display bullets when the user types into it, instead of echo'ing the text color_theme (optional): The color_theme for this edit field. By default the parent window's theme is used. color_theme.edit_field is used as the background color; color_theme.highlight_edit is the background when this InputWindow is active. ''' if color_theme is None: color_theme = window.color_theme color = color_theme.edit_field highlight_color = color_theme.highlight_edit area.lines = 1 super(InputWindow, self).__init__(area, window, color_theme, color, highlight_color, at_index) self.masked = masked self.masked_char = ord('*') self.old_cursor_state = 0 self.textbox = Textbox(self.window) self.textbox.stripspaces = True self.set_text(text) self.text_buff = [] def set_text(self, text): self.text = [] for char in text: self.textbox.do_command(self.handle_input(ord(char))) self.no_ut_refresh() def set_repo(self, text): self.text = [] self.textbox.do_command(ord(ctrl('a'))) self.textbox.do_command(ord(ctrl('k'))) for char in text: self.textbox.do_command(self.handle_input(ord(char))) self.no_ut_refresh() def handle_input(self, input): input = self.translate_input(input) if input in range(curses.KEY_F0, curses.KEY_F10) or \ input == curses.KEY_UP or input == curses.KEY_DOWN: logging.debug("Got special key, breaking") self.input = input return ord(ctrl('g')) else: self.input = None if input is not None and isprint(input): self.text_buff.append(chr(input)) self.text.append(input) if not self.is_valid(): if len(self.text) > 0: self.text.pop() length = len(self.text_buff) temp_buff = self.text_buff[:length-49] self.set_repo(self.text_buff[length-49:]) length = len(self.text_buff) self.text_buff = temp_buff + self.text_buff[length-49:] if self.masked: input = self.masked_char elif input == curses.KEY_BACKSPACE or input == ord(ctrl('H')): if len(self.text) > 0: self.text.pop() self.text_buff.pop() return input def process(self, input): try: self.old_cursor_state = curses.curs_set(2) except curses.error: logging.debug("Got curses.error when enabling cursor") # Put input back on stack so that textbox.edit can read it try: curses.ungetch(input) except TypeError: pass self.textbox.do_command(ord(ctrl('e'))) self.textbox.edit(self.handle_input) try: self.old_cursor_state = curses.curs_set(self.old_cursor_state) except curses.error: logging.debug("Got curses.error when reverting cursor") logging.debug("Returning: " + str(self.input)) return self.input def is_special_char(self, input_key): '''Check to see if this is a keystroke that should break us out of adding input to the Textbox and return control to the parent window ''' if (input_key in range(curses.KEY_F0, curses.KEY_F10) or input_key in self.key_dict): return True else: return False def is_valid(self): win_size_x = self.window.getmaxyx()[1] return len(self.text) < win_size_x
class cursesWindow(): __shared_state = {} def __init__(self, pad = None): self.__dict__ = self.__shared_state if not pad: pad = cursesPad() self.pad = pad self.screen = '' self.window = '' self.textbox = '' self.size = os.popen('stty size', 'r').read().split() def initialize(self): self.initializeWindow() self.initializeColorScheme() def initializeWindow(self): self.screen = curses.initscr() curses.start_color() curses.use_default_colors() curses.noecho() curses.cbreak() self.screen.keypad(1) self.setCursorVisibility(0) self.window = curses.newwin(int(self.size[0]), int(self.size[1]), 0, 0) signal.signal(signal.SIGWINCH, self.windowSizeChangeEventHandler) def initializeColorScheme(self): curses.init_pair(2, curses.COLOR_RED, -1) curses.init_pair(3, curses.COLOR_GREEN, -1) curses.init_pair(4, curses.COLOR_YELLOW, -1) curses.init_pair(5, curses.COLOR_BLUE, -1) curses.init_pair(6, curses.COLOR_MAGENTA, -1) curses.init_pair(7, curses.COLOR_CYAN, -1) curses.init_pair(8, curses.COLOR_WHITE, -1) def windowSizeChangeEventHandler(self, n, frame): self.size = os.popen('stty size', 'r').read().split() def setCursorVisibility(self, visible): try: curses.curs_set(visible) except: pass def getUserInput(self): return self.screen.getch() def close(self): self.setCursorVisibility(1) curses.nocbreak() self.screen.keypad(0) curses.echo() curses.endwin() def getInput(self, edit_window): self.setCursorVisibility(1) value = curses.textpad.Textbox(edit_window, insert_mode = True).edit().strip() self.setCursorVisibility(0) return value def wait(self, delay): self.screen.timeout(delay) def refresh(self): try: self.screen.noutrefresh() except: pass def update(self): curses.doupdate() def clear(self): self.screen.clear() self.pad.clear() # Drawing def addStr(self, string, color = ''): try: if not color: self.screen.addstr(string) else: self.screen.addstr(string, curses.color_pair(color)) except: pass def addString(self, posX, posY, string, color = ''): try: if not color: self.screen.addstr(posY, posX, string) else: self.screen.addstr(posY, posX, string, curses.color_pair(color)) except: pass def draw(self, screen): if not screen: return if len(screen[0]) == 4: for (y, x, line, color) in screen: self.addString(y, x, line, color) elif len(screen[0]) == 2: for (line, color) in screen: self.addStr(line, color) def editTextOnScreen(self, line_index, line_contents): editwin = self.window.subwin(1, int(self.size[1]) - 33, line_index, 7) editwin.addstr(0, 0, ' ' * (int(self.size[1]) - 34)) editwin.addstr(0, 0, line_contents) return self.editTextField(editwin) def editTextField(self, editwin): self.setCursorVisibility(1) self.textbox = Textbox(editwin, insert_mode = True) newname = self.textbox.edit(self.deleteKeyPressHandler).strip() self.textbox = '' self.setCursorVisibility(0) return newname def deleteKeyPressHandler(self, keyPressed): if keyPressed in (330, 263, 127): # Delete self.textbox.do_command(curses.KEY_BACKSPACE) else: return keyPressed return