From 503c2f03ec515d3ad7894d26bc7b309e7999d1bd Mon Sep 17 00:00:00 2001 From: telday Date: Fri, 6 Mar 2020 21:01:11 -0500 Subject: [PATCH 01/20] Added keymap class and make keys an enum --- py_cui/keys.py | 206 +++++++++++++++++++++++++++++-------------------- 1 file changed, 123 insertions(+), 83 deletions(-) diff --git a/py_cui/keys.py b/py_cui/keys.py index 0b1d67a..5926b10 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -5,6 +5,7 @@ """ from sys import platform +from typing import Callable import curses # Some simple helper functions @@ -22,7 +23,6 @@ def get_ascii_from_char(char): ascii_code : int Ascii code of character """ - return ord(char) @@ -39,90 +39,130 @@ def get_char_from_ascii(key_num): char : character character converted from ascii """ - return chr(key_num) +class KeyMap(object): + def __init__(self): + self._bindings = dict() + + def bind_key(self, /, key: Key, *, match: Callable=None, definition: Callable=None, old: Key=None): + """Binds a key to either a function or a key that previously had a binding + + Parameters + ---------- + key : Key + The new key to bind + definition : Callable + The function to bind + old : Key + The already bound key to bind to + """ + if not isinstance(key, Key): + raise ValueError(f"{key} is an invalid value for key") + if old and old.value not in self._bindings: + raise ValueError(f"{old} is not in the bindings list, cannot bind to it") + if not old and not definition: + raise ValueError(f"Either old or definition must be defined") + if old and definition: + raise ValueError(f"Cannot bind to both a key and a callable") + if old: + self._bindings[key.value] = self._bindings[old.value] + self._bindings[key.value] = definition + + def execute(self, key: Key): + if not isinstance(key, Key): + raise ValueError(f"{key} is an invalid value for key") + elif key not in self._bindings.keys(): + raise ValueError(f"{key} is an invalid value for key") + + self._bindings[key.value]() + + def unbind(self, key: Key): + """Unbinds a key from the map -# Supported py_cui keys -KEY_ENTER = get_ascii_from_char('\n') -# Escape character is ascii #27 -KEY_ESCAPE = 27 -KEY_SPACE = get_ascii_from_char(' ') -KEY_DELETE = curses.KEY_DC -KEY_TAB = get_ascii_from_char('\t') -KEY_UP_ARROW = curses.KEY_UP -KEY_DOWN_ARROW = curses.KEY_DOWN -KEY_LEFT_ARROW = curses.KEY_LEFT -KEY_RIGHT_ARROW = curses.KEY_RIGHT -KEY_PAGE_UP = curses.KEY_PPAGE -KEY_PAGE_DOWN = curses.KEY_NPAGE -KEY_F1 = curses.KEY_F1 -KEY_F2 = curses.KEY_F2 -KEY_F3 = curses.KEY_F3 -KEY_F4 = curses.KEY_F4 -KEY_F5 = curses.KEY_F5 -KEY_F6 = curses.KEY_F6 -KEY_F7 = curses.KEY_F7 -KEY_F8 = curses.KEY_F8 -KEY_HOME = curses.KEY_HOME -KEY_END = curses.KEY_END -KEY_A_LOWER = get_ascii_from_char('a') -KEY_B_LOWER = get_ascii_from_char('b') -KEY_C_LOWER = get_ascii_from_char('c') -KEY_D_LOWER = get_ascii_from_char('d') -KEY_E_LOWER = get_ascii_from_char('e') -KEY_F_LOWER = get_ascii_from_char('f') -KEY_G_LOWER = get_ascii_from_char('g') -KEY_H_LOWER = get_ascii_from_char('h') -KEY_I_LOWER = get_ascii_from_char('i') -KEY_J_LOWER = get_ascii_from_char('j') -KEY_K_LOWER = get_ascii_from_char('k') -KEY_L_LOWER = get_ascii_from_char('l') -KEY_M_LOWER = get_ascii_from_char('m') -KEY_N_LOWER = get_ascii_from_char('n') -KEY_O_LOWER = get_ascii_from_char('o') -KEY_P_LOWER = get_ascii_from_char('p') -KEY_Q_LOWER = get_ascii_from_char('q') -KEY_R_LOWER = get_ascii_from_char('r') -KEY_S_LOWER = get_ascii_from_char('s') -KEY_T_LOWER = get_ascii_from_char('t') -KEY_U_LOWER = get_ascii_from_char('u') -KEY_V_LOWER = get_ascii_from_char('v') -KEY_W_LOWER = get_ascii_from_char('w') -KEY_X_LOWER = get_ascii_from_char('x') -KEY_Y_LOWER = get_ascii_from_char('y') -KEY_Z_LOWER = get_ascii_from_char('z') -KEY_A_UPPER = get_ascii_from_char('A') -KEY_B_UPPER = get_ascii_from_char('B') -KEY_C_UPPER = get_ascii_from_char('C') -KEY_D_UPPER = get_ascii_from_char('D') -KEY_E_UPPER = get_ascii_from_char('E') -KEY_F_UPPER = get_ascii_from_char('F') -KEY_G_UPPER = get_ascii_from_char('G') -KEY_H_UPPER = get_ascii_from_char('H') -KEY_I_UPPER = get_ascii_from_char('I') -KEY_J_UPPER = get_ascii_from_char('J') -KEY_K_UPPER = get_ascii_from_char('K') -KEY_L_UPPER = get_ascii_from_char('L') -KEY_M_UPPER = get_ascii_from_char('M') -KEY_N_UPPER = get_ascii_from_char('N') -KEY_O_UPPER = get_ascii_from_char('O') -KEY_P_UPPER = get_ascii_from_char('P') -KEY_Q_UPPER = get_ascii_from_char('Q') -KEY_R_UPPER = get_ascii_from_char('R') -KEY_S_UPPER = get_ascii_from_char('S') -KEY_T_UPPER = get_ascii_from_char('T') -KEY_U_UPPER = get_ascii_from_char('U') -KEY_V_UPPER = get_ascii_from_char('V') -KEY_W_UPPER = get_ascii_from_char('W') -KEY_X_UPPER = get_ascii_from_char('X') -KEY_Y_UPPER = get_ascii_from_char('Y') -KEY_Z_UPPER = get_ascii_from_char('Z') - -# Pressing backspace returns 8 on windows? -if platform == 'win32': - KEY_BACKSPACE = 8 -else: - KEY_BACKSPACE = curses.KEY_BACKSPACE + Parameters + ---------- + key : Key + The key to unbind + """ + while key.value in self._bindings: + del self._bindings[key.value] +class Key(enum.Enum): + # Supported py_cui keys + KEY_ENTER = get_ascii_from_char('\n') + # Escape character is ascii #27 + KEY_ESCAPE = 27 + KEY_SPACE = get_ascii_from_char(' ') + KEY_DELETE = curses.KEY_DC + KEY_TAB = get_ascii_from_char('\t') + KEY_UP_ARROW = curses.KEY_UP + KEY_DOWN_ARROW = curses.KEY_DOWN + KEY_LEFT_ARROW = curses.KEY_LEFT + KEY_RIGHT_ARROW = curses.KEY_RIGHT + KEY_PAGE_UP = curses.KEY_PPAGE + KEY_PAGE_DOWN = curses.KEY_NPAGE + KEY_F1 = curses.KEY_F1 + KEY_F2 = curses.KEY_F2 + KEY_F3 = curses.KEY_F3 + KEY_F4 = curses.KEY_F4 + KEY_F5 = curses.KEY_F5 + KEY_F6 = curses.KEY_F6 + KEY_F7 = curses.KEY_F7 + KEY_F8 = curses.KEY_F8 + KEY_HOME = curses.KEY_HOME + KEY_END = curses.KEY_END + KEY_A_LOWER = get_ascii_from_char('a') + KEY_B_LOWER = get_ascii_from_char('b') + KEY_C_LOWER = get_ascii_from_char('c') + KEY_D_LOWER = get_ascii_from_char('d') + KEY_E_LOWER = get_ascii_from_char('e') + KEY_F_LOWER = get_ascii_from_char('f') + KEY_G_LOWER = get_ascii_from_char('g') + KEY_H_LOWER = get_ascii_from_char('h') + KEY_I_LOWER = get_ascii_from_char('i') + KEY_J_LOWER = get_ascii_from_char('j') + KEY_K_LOWER = get_ascii_from_char('k') + KEY_L_LOWER = get_ascii_from_char('l') + KEY_M_LOWER = get_ascii_from_char('m') + KEY_N_LOWER = get_ascii_from_char('n') + KEY_O_LOWER = get_ascii_from_char('o') + KEY_P_LOWER = get_ascii_from_char('p') + KEY_Q_LOWER = get_ascii_from_char('q') + KEY_R_LOWER = get_ascii_from_char('r') + KEY_S_LOWER = get_ascii_from_char('s') + KEY_T_LOWER = get_ascii_from_char('t') + KEY_U_LOWER = get_ascii_from_char('u') + KEY_V_LOWER = get_ascii_from_char('v') + KEY_W_LOWER = get_ascii_from_char('w') + KEY_X_LOWER = get_ascii_from_char('x') + KEY_Y_LOWER = get_ascii_from_char('y') + KEY_Z_LOWER = get_ascii_from_char('z') + KEY_A_UPPER = get_ascii_from_char('A') + KEY_B_UPPER = get_ascii_from_char('B') + KEY_C_UPPER = get_ascii_from_char('C') + KEY_D_UPPER = get_ascii_from_char('D') + KEY_E_UPPER = get_ascii_from_char('E') + KEY_F_UPPER = get_ascii_from_char('F') + KEY_G_UPPER = get_ascii_from_char('G') + KEY_H_UPPER = get_ascii_from_char('H') + KEY_I_UPPER = get_ascii_from_char('I') + KEY_J_UPPER = get_ascii_from_char('J') + KEY_K_UPPER = get_ascii_from_char('K') + KEY_L_UPPER = get_ascii_from_char('L') + KEY_M_UPPER = get_ascii_from_char('M') + KEY_N_UPPER = get_ascii_from_char('N') + KEY_O_UPPER = get_ascii_from_char('O') + KEY_P_UPPER = get_ascii_from_char('P') + KEY_Q_UPPER = get_ascii_from_char('Q') + KEY_R_UPPER = get_ascii_from_char('R') + KEY_S_UPPER = get_ascii_from_char('S') + KEY_T_UPPER = get_ascii_from_char('T') + KEY_U_UPPER = get_ascii_from_char('U') + KEY_V_UPPER = get_ascii_from_char('V') + KEY_W_UPPER = get_ascii_from_char('W') + KEY_X_UPPER = get_ascii_from_char('X') + KEY_Y_UPPER = get_ascii_from_char('Y') + KEY_Z_UPPER = get_ascii_from_char('Z') + KEY_BACKSPACE = 8 if platform == 'win32' else curses.KEY_BACKSPACE From 357741bef09467909a0ced7b731d26722fc38293 Mon Sep 17 00:00:00 2001 From: telday Date: Fri, 6 Mar 2020 22:30:59 -0500 Subject: [PATCH 02/20] update gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 11c780c..1e47e41 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ notes/ markdoc/ npdoc2md/ *.*~ -ecui/ +epy_cui/ +activate.bat +*.swp From f26f3de1825f62f0c97fad4dbb167693ac7993c6 Mon Sep 17 00:00:00 2001 From: telday Date: Fri, 6 Mar 2020 23:47:19 -0500 Subject: [PATCH 03/20] Changed key names --- py_cui/keys.py | 169 +++++++++++++++++++++++++------------------------ 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/py_cui/keys.py b/py_cui/keys.py index 5926b10..d6864bf 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -7,6 +7,7 @@ from sys import platform from typing import Callable import curses +import enum # Some simple helper functions @@ -41,11 +42,92 @@ def get_char_from_ascii(key_num): """ return chr(key_num) + +class Key(enum.Enum): + # KeysSupported py_cui keys + ENTER = get_ascii_from_char('\n') + #Esccape character is ascii #27 + ESCAPE = 27 + SPACE = get_ascii_from_char(' ') + DELETE = curses.KEY_DC + TAB = get_ascii_from_char('\t') + UP_ARROW = curses.KEY_UP + DOWN_ARROW = curses.KEY_DOWN + LEFT_ARROW = curses.KEY_LEFT + RIGHT_ARROW = curses.KEY_RIGHT + PAGE_UP = curses.KEY_PPAGE + PAGE_DOWN = curses.KEY_NPAGE + F1 = curses.KEY_F1 + F2 = curses.KEY_F2 + F3 = curses.KEY_F3 + F4 = curses.KEY_F4 + F5 = curses.KEY_F5 + F6 = curses.KEY_F6 + F7 = curses.KEY_F7 + F8 = curses.KEY_F8 + HOME = curses.KEY_HOME + END = curses.KEY_END + A_LOWER = get_ascii_from_char('a') + B_LOWER = get_ascii_from_char('b') + C_LOWER = get_ascii_from_char('c') + D_LOWER = get_ascii_from_char('d') + E_LOWER = get_ascii_from_char('e') + F_LOWER = get_ascii_from_char('f') + G_LOWER = get_ascii_from_char('g') + H_LOWER = get_ascii_from_char('h') + I_LOWER = get_ascii_from_char('i') + J_LOWER = get_ascii_from_char('j') + K_LOWER = get_ascii_from_char('k') + L_LOWER = get_ascii_from_char('l') + M_LOWER = get_ascii_from_char('m') + N_LOWER = get_ascii_from_char('n') + O_LOWER = get_ascii_from_char('o') + P_LOWER = get_ascii_from_char('p') + Q_LOWER = get_ascii_from_char('q') + R_LOWER = get_ascii_from_char('r') + S_LOWER = get_ascii_from_char('s') + T_LOWER = get_ascii_from_char('t') + U_LOWER = get_ascii_from_char('u') + V_LOWER = get_ascii_from_char('v') + W_LOWER = get_ascii_from_char('w') + X_LOWER = get_ascii_from_char('x') + Y_LOWER = get_ascii_from_char('y') + Z_LOWER = get_ascii_from_char('z') + A_UPPER = get_ascii_from_char('A') + B_UPPER = get_ascii_from_char('B') + C_UPPER = get_ascii_from_char('C') + D_UPPER = get_ascii_from_char('D') + E_UPPER = get_ascii_from_char('E') + F_UPPER = get_ascii_from_char('F') + G_UPPER = get_ascii_from_char('G') + H_UPPER = get_ascii_from_char('H') + I_UPPER = get_ascii_from_char('I') + J_UPPER = get_ascii_from_char('J') + K_UPPER = get_ascii_from_char('K') + L_UPPER = get_ascii_from_char('L') + M_UPPER = get_ascii_from_char('M') + N_UPPER = get_ascii_from_char('N') + O_UPPER = get_ascii_from_char('O') + P_UPPER = get_ascii_from_char('P') + Q_UPPER = get_ascii_from_char('Q') + R_UPPER = get_ascii_from_char('R') + S_UPPER = get_ascii_from_char('S') + T_UPPER = get_ascii_from_char('T') + U_UPPER = get_ascii_from_char('U') + V_UPPER = get_ascii_from_char('V') + W_UPPER = get_ascii_from_char('W') + X_UPPER = get_ascii_from_char('X') + Y_UPPER = get_ascii_from_char('Y') + Z_UPPER = get_ascii_from_char('Z') + + BACKSPACE = 8 if platform == 'win32' else curses.KEY_BACKSPACE + + class KeyMap(object): def __init__(self): self._bindings = dict() - def bind_key(self, /, key: Key, *, match: Callable=None, definition: Callable=None, old: Key=None): + def bind_key(self, /, key: Key, *, definition: Callable=None, old: Key=None): """Binds a key to either a function or a key that previously had a binding Parameters @@ -72,10 +154,10 @@ def bind_key(self, /, key: Key, *, match: Callable=None, definition: Callable=No def execute(self, key: Key): if not isinstance(key, Key): raise ValueError(f"{key} is an invalid value for key") - elif key not in self._bindings.keys(): - raise ValueError(f"{key} is an invalid value for key") + elif key.value not in self._bindings.keys(): + return - self._bindings[key.value]() + self._bindings[key.value](key) def unbind(self, key: Key): """Unbinds a key from the map @@ -87,82 +169,3 @@ def unbind(self, key: Key): """ while key.value in self._bindings: del self._bindings[key.value] - -class Key(enum.Enum): - # Supported py_cui keys - KEY_ENTER = get_ascii_from_char('\n') - # Escape character is ascii #27 - KEY_ESCAPE = 27 - KEY_SPACE = get_ascii_from_char(' ') - KEY_DELETE = curses.KEY_DC - KEY_TAB = get_ascii_from_char('\t') - KEY_UP_ARROW = curses.KEY_UP - KEY_DOWN_ARROW = curses.KEY_DOWN - KEY_LEFT_ARROW = curses.KEY_LEFT - KEY_RIGHT_ARROW = curses.KEY_RIGHT - KEY_PAGE_UP = curses.KEY_PPAGE - KEY_PAGE_DOWN = curses.KEY_NPAGE - KEY_F1 = curses.KEY_F1 - KEY_F2 = curses.KEY_F2 - KEY_F3 = curses.KEY_F3 - KEY_F4 = curses.KEY_F4 - KEY_F5 = curses.KEY_F5 - KEY_F6 = curses.KEY_F6 - KEY_F7 = curses.KEY_F7 - KEY_F8 = curses.KEY_F8 - KEY_HOME = curses.KEY_HOME - KEY_END = curses.KEY_END - KEY_A_LOWER = get_ascii_from_char('a') - KEY_B_LOWER = get_ascii_from_char('b') - KEY_C_LOWER = get_ascii_from_char('c') - KEY_D_LOWER = get_ascii_from_char('d') - KEY_E_LOWER = get_ascii_from_char('e') - KEY_F_LOWER = get_ascii_from_char('f') - KEY_G_LOWER = get_ascii_from_char('g') - KEY_H_LOWER = get_ascii_from_char('h') - KEY_I_LOWER = get_ascii_from_char('i') - KEY_J_LOWER = get_ascii_from_char('j') - KEY_K_LOWER = get_ascii_from_char('k') - KEY_L_LOWER = get_ascii_from_char('l') - KEY_M_LOWER = get_ascii_from_char('m') - KEY_N_LOWER = get_ascii_from_char('n') - KEY_O_LOWER = get_ascii_from_char('o') - KEY_P_LOWER = get_ascii_from_char('p') - KEY_Q_LOWER = get_ascii_from_char('q') - KEY_R_LOWER = get_ascii_from_char('r') - KEY_S_LOWER = get_ascii_from_char('s') - KEY_T_LOWER = get_ascii_from_char('t') - KEY_U_LOWER = get_ascii_from_char('u') - KEY_V_LOWER = get_ascii_from_char('v') - KEY_W_LOWER = get_ascii_from_char('w') - KEY_X_LOWER = get_ascii_from_char('x') - KEY_Y_LOWER = get_ascii_from_char('y') - KEY_Z_LOWER = get_ascii_from_char('z') - KEY_A_UPPER = get_ascii_from_char('A') - KEY_B_UPPER = get_ascii_from_char('B') - KEY_C_UPPER = get_ascii_from_char('C') - KEY_D_UPPER = get_ascii_from_char('D') - KEY_E_UPPER = get_ascii_from_char('E') - KEY_F_UPPER = get_ascii_from_char('F') - KEY_G_UPPER = get_ascii_from_char('G') - KEY_H_UPPER = get_ascii_from_char('H') - KEY_I_UPPER = get_ascii_from_char('I') - KEY_J_UPPER = get_ascii_from_char('J') - KEY_K_UPPER = get_ascii_from_char('K') - KEY_L_UPPER = get_ascii_from_char('L') - KEY_M_UPPER = get_ascii_from_char('M') - KEY_N_UPPER = get_ascii_from_char('N') - KEY_O_UPPER = get_ascii_from_char('O') - KEY_P_UPPER = get_ascii_from_char('P') - KEY_Q_UPPER = get_ascii_from_char('Q') - KEY_R_UPPER = get_ascii_from_char('R') - KEY_S_UPPER = get_ascii_from_char('S') - KEY_T_UPPER = get_ascii_from_char('T') - KEY_U_UPPER = get_ascii_from_char('U') - KEY_V_UPPER = get_ascii_from_char('V') - KEY_W_UPPER = get_ascii_from_char('W') - KEY_X_UPPER = get_ascii_from_char('X') - KEY_Y_UPPER = get_ascii_from_char('Y') - KEY_Z_UPPER = get_ascii_from_char('Z') - - KEY_BACKSPACE = 8 if platform == 'win32' else curses.KEY_BACKSPACE From f51262964217e5b22d8dd099c07022e463416019 Mon Sep 17 00:00:00 2001 From: telday Date: Fri, 6 Mar 2020 23:47:34 -0500 Subject: [PATCH 04/20] Added __add__ to keymap --- py_cui/keys.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/py_cui/keys.py b/py_cui/keys.py index d6864bf..42950d9 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -169,3 +169,11 @@ def unbind(self, key: Key): """ while key.value in self._bindings: del self._bindings[key.value] + + def __add__(self, value): + if not isinstance(value, self.__class__): + raise ValueError(f"Cannot add a KeyMap and {value.__class__} type") + else: + k = KeyMap() + k._bindings = {**value._binings, **self._bindings} + return k From 3cefdc1dbf60f3dc1ac8c5c0b4f2580a5e686d52 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 00:04:25 -0500 Subject: [PATCH 05/20] Added new key_map to PyCUI --- py_cui/__init__.py | 154 ++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 72 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index a70cd3d..5f121b8 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -104,11 +104,9 @@ class PyCUI: list of keybindings to check against in the main CUI loop height, width : int height of the terminal in characters, width of terminal in characters - exit_key : key_code - a key code for a key that exits the CUI """ - def __init__(self, num_rows, num_cols, auto_focus_buttons=True, exit_key=py_cui.keys.KEY_Q_LOWER): + def __init__(self, num_rows, num_cols, auto_focus_buttons=True): """Constructor for PyCUI class """ @@ -125,7 +123,7 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, exit_key=py_cui. # Add status and title bar self.title_bar = py_cui.statusbar.StatusBar(self.title, BLACK_ON_WHITE) - self.init_status_bar_text = 'Press - {} - to exit. Arrow Keys to move between widgets. Enter to enter focus mode.'.format(py_cui.keys.get_char_from_ascii(exit_key)) + self.init_status_bar_text = 'Press - {} - to exit. Arrow Keys to move between widgets. Enter to enter focus mode.'.format(py_cui.keys.get_char_from_ascii(py_cui.keys.Key.Q_LOWER.value)) self.status_bar = py_cui.statusbar.StatusBar(self.init_status_bar_text, BLACK_ON_WHITE) # Initialize grid, renderer, and widget dict @@ -147,12 +145,21 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, exit_key=py_cui. self.post_loading_callback = None # Top level keybindings. Exit key is 'q' by default - self.keybindings = {} - self.exit_key = exit_key + self.key_map = py_cui.keys.KeyMap() + self.load_default_keymap() # Callback to fire when CUI is stopped. self.on_stop = None + def load_default_keymap(self): + stop_wrapper = lambda x: self.stop() + self.key_map.bind_key(key=py_cui.keys.Key.Q_LOWER, definition=stop_wrapper) + self.key_map.bind_key(key=py_cui.keys.Key.ESCAPE, definition=self.lose_focus) + self.key_map.bind_key(key=py_cui.keys.Key.ENTER, definition=self.select_widget) + self.key_map.bind_key(key=py_cui.keys.Key.UP_ARROW, definition=self.move_to_neighbor) + self.key_map.bind_key(key=py_cui.keys.Key.DOWN_ARROW, definition=self.move_to_neighbor) + self.key_map.bind_key(key=py_cui.keys.Key.LEFT_ARROW, definition=self.move_to_neighbor) + self.key_map.bind_key(key=py_cui.keys.Key.RIGHT_ARROW, definition=self.move_to_neighbor) def get_widget_set(self): """Gets widget set object from current widgets. @@ -625,7 +632,7 @@ def check_if_neighbor_exists(self, row, column, direction): row of current widget column : int column of current widget - direction : py_cui.keys.KEY_* + direction : py_cui.keys.Key.* The direction in which to search Returns @@ -637,10 +644,10 @@ def check_if_neighbor_exists(self, row, column, direction): start_widget = self.selected_widget # Find all the widgets in the given row or column - if direction in [py_cui.keys.KEY_DOWN_ARROW, py_cui.keys.KEY_UP_ARROW]: + if direction in [py_cui.keys.Key.DOWN_ARROW, py_cui.keys.Key.UP_ARROW]: widgets = [i.id for i in self.get_widgets_by_col(column)] vertical = True - elif direction in [py_cui.keys.KEY_RIGHT_ARROW, py_cui.keys.KEY_LEFT_ARROW]: + elif direction in [py_cui.keys.Key.RIGHT_ARROW, py_cui.keys.Key.LEFT_ARROW]: widgets = [i.id for i in self.get_widgets_by_row(row)] vertical = False else: @@ -653,9 +660,9 @@ def check_if_neighbor_exists(self, row, column, direction): # Find the widget and move from there current_index = widgets.index(start_widget) - if direction == py_cui.keys.KEY_UP_ARROW or direction == py_cui.keys.KEY_LEFT_ARROW: + if direction is py_cui.keys.Key.UP_ARROW or direction is py_cui.keys.Key.LEFT_ARROW: current_index -= 1 - elif direction == py_cui.keys.KEY_DOWN_ARROW or direction == py_cui.keys.KEY_RIGHT_ARROW: + elif direction is py_cui.keys.Key.DOWN_ARROW or direction is py_cui.keys.Key.RIGHT_ARROW: current_index += 1 if current_index >= len(widgets) or current_index < 0: @@ -720,21 +727,6 @@ def move_focus(self, widget): self.in_focused_mode = True self.status_bar.set_text(widget.get_help_text()) - - def add_key_command(self, key, command): - """Function that adds a keybinding to the CUI when in overview mode - - Parameters - ---------- - key : py_cui.keys.KEY_* - The key bound to the command - command : Function - A no-arg or lambda function to fire on keypress - """ - - self.keybindings[key] = command - - # Popup functions. Used to display messages, warnings, and errors to the user. def show_message_popup(self, title, text): @@ -986,61 +978,79 @@ def display_window_warning(self, stdscr, error_info): except KeyboardInterrupt: exit() + def handle_lose_focus(self, key: py_cui.keys.Key): + """Moves the focus out of a widget if it is currently set in one + + Parameters + ---------- + key : Key + The key pressed to trigger this method + """ + selected_widget = self.widgets[self.selected_widget] + if self.in_focused_mode and self.popup is None: + self.lose_focus() + + def select_widget(self, key: py_cui.keys.Key): + """Attempts to put the user in focus to a widget + + Parameters + ---------- + key : Key + The key pressed to trigger this method + """ + selected_widget = self.widgets[self.selected_widget] + + if self.popup is None and self.selected_widget is not None and selected_widget.is_selectable: + self.in_focused_mode = True + selected_widget.selected = True + # If autofocus buttons is selected, we automatically process the button command and reset to overview mode + if self.auto_focus_buttons and isinstance(selected_widget, widgets.Button): + self.in_focused_mode = False + selected_widget.selected = False + if selected_widget.command is not None: + selected_widget.command() + else: + self.status_bar.set_text(selected_widget.get_help_text()) + + def move_to_neighbor(self, direction: py_cui.keys.Key): + """Attempts to move the cursor to one of the neighbor widgets based on the key pressed + + Parameters + ---------- + direction : py_cui.keys.Key + The directional key pressed + """ + selected_widget = self.widgets[self.selected_widget] + + neighbor = self.check_if_neighbor_exists(selected_widget.row, selected_widget.column, direction) + if neighbor: + selected_widget.selected = False + self.set_selected_widget(neighbor) def handle_key_presses(self, key_pressed): """Function that handles all main loop key presses. Parameters ---------- - key_pressed : py_cui.keys.KEY_* + key_pressed : py_cui.keys.Key.* The key being pressed """ - + print(key_pressed.value, key_pressed.name) # Selected widget represents which widget is being hovered over, though not necessarily in focus mode if self.selected_widget is None: return - selected_widget = self.widgets[self.selected_widget] - - # If we are in focus mode, the widget has all of the control of the keyboard except - # for the escape key, which exits focus mode. - if self.in_focused_mode and self.popup is None: - if key_pressed == py_cui.keys.KEY_ESCAPE: - self.status_bar.set_text(self.init_status_bar_text) - self.in_focused_mode = False - selected_widget.selected = False - else: - # widget handles remaining py_cui.keys - selected_widget.handle_key_press(key_pressed) # Otherwise, barring a popup, we are in overview mode, meaning that arrow py_cui.keys move between widgets, and Enter key starts focus mode - elif self.popup is None: - if key_pressed == py_cui.keys.KEY_ENTER and self.selected_widget is not None and selected_widget.is_selectable: - self.in_focused_mode = True - selected_widget.selected = True - # If autofocus buttons is selected, we automatically process the button command and reset to overview mode - if self.auto_focus_buttons and isinstance(selected_widget, widgets.Button): - self.in_focused_mode = False - selected_widget.selected = False - if selected_widget.command is not None: - selected_widget.command() - else: - self.status_bar.set_text(selected_widget.get_help_text()) - for key in self.keybindings.keys(): - if key_pressed == key: - command = self.keybindings[key] - command() - - # If not in focus mode, use the arrow py_cui.keys to move around the selectable widgets. - neighbor = None - if key_pressed == py_cui.keys.KEY_UP_ARROW or key_pressed == py_cui.keys.KEY_DOWN_ARROW or key_pressed == py_cui.keys.KEY_LEFT_ARROW or key_pressed == py_cui.keys.KEY_RIGHT_ARROW: - neighbor = self.check_if_neighbor_exists(selected_widget.row, selected_widget.column, key_pressed) - if neighbor is not None: - selected_widget.selected = False - self.set_selected_widget(neighbor) - + if not self.popup and self.selected_widget and self.in_focused_mode: + self.widgets[self.selected_widget].handle_key_press(key_pressed) + print('processed widget handler') # if we have a popup, that takes key control from both overview and focus mode elif self.popup is not None: + print('popup handler') self.popup.handle_key_press(key_pressed) + else: + print('executing main keymap') + self.key_map.execute(key_pressed) def draw(self, stdscr): @@ -1068,10 +1078,7 @@ def draw(self, stdscr): self.renderer.set_border_renderer_chars(self.border_characters) # Loop where key_pressed is the last character pressed. Wait for exit key while no popup or focus mode - while key_pressed != self.exit_key or self.in_focused_mode or self.popup is not None: - - if self.stopped: - break + while not self.stopped or self.in_focused_mode or self.popup is not None: # Initialization and size adjustment stdscr.clear() @@ -1093,7 +1100,12 @@ def draw(self, stdscr): self.post_loading_callback = None # Handle keypresses - self.handle_key_presses(key_pressed) + if key_pressed != 0: + try: + key = py_cui.keys.Key(key_pressed) + self.handle_key_presses(key) + except ValueError as e: + continue # Draw status/title bar, and all widgets. Selected widget will be bolded. self.draw_status_bars(stdscr, height, width) @@ -1111,8 +1123,6 @@ def draw(self, stdscr): time.sleep(0.25) # Need to reset key_pressed, because otherwise the previously pressed key will be used. key_pressed = 0 - elif self.stopped: - key_pressed = self.exit_key else: key_pressed = stdscr.getch() From 0045490ee9164526c4f023014f7b222cd43a12cb Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 00:17:23 -0500 Subject: [PATCH 06/20] Added key maps to widgets --- py_cui/widgets.py | 238 +++++++++++++++------------------------------- 1 file changed, 77 insertions(+), 161 deletions(-) diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 494c1cb..cfb5d0e 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -93,8 +93,8 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.selected = False self.is_selectable = selectable self.help_text = 'No help text available.' - self.key_commands = {} self.text_color_rules = [] + self.key_map = py_cui.keys.KeyMap() def set_focus_text(self, text): @@ -108,21 +108,6 @@ def set_focus_text(self, text): self.help_text = text - - def add_key_command(self, key, command): - """Maps a keycode to a function that will be executed when in focus mode - - Parameters - ---------- - key : py_cui.keys.KEY - ascii keycode used to map the key - command : function without args - a non-argument function or lambda function to execute if in focus mode and key is pressed - """ - - self.key_commands[key] = command - - def add_text_color_rule(self, regex, color, rule_type, match_type='line', region=[0,1], include_whitespace=False): """Forces renderer to draw text using given color if text_condition_function returns True @@ -291,11 +276,7 @@ def handle_key_press(self, key_pressed): key_pressed : int key code of key pressed """ - - if key_pressed in self.key_commands.keys(): - command = self.key_commands[key_pressed] - command() - + self.key_map.execute(key_pressed) def draw(self): """Base class draw class that checks if renderer is valid. @@ -418,7 +399,8 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.selected_item = 0 self.view_items = [] self.set_focus_text('Focus mode on ScrollMenu. Use up/down to scroll, Enter to trigger command, Esc to exit.') - + self.key_map.bind_key(key=py_cui.keys.Key.UP_ARROW, definition=self.scroll_up) + self.key_map.bind_key(key=py_cui.keys.Key.DOWN_ARROW, definition=self.scroll_down) def clear(self): """Clears all items from the Scroll Menu @@ -429,8 +411,13 @@ def clear(self): self.top_view = 0 - def scroll_up(self): + def scroll_up(self, key: py_cui.keys.Key): """Function that scrolls the view up in the scroll menu + + Parameters + ---------- + key : Key + The key pressed to trigger this event """ if self.selected: @@ -440,10 +427,14 @@ def scroll_up(self): self.selected_item = self.selected_item - 1 - def scroll_down(self): + def scroll_down(self, key: py_cui.keys.Key): """Function that scrolls the view down in the scroll menu - """ + Parameters + ---------- + key : Key + The key pressed to trigger this event + """ if self.selected: if self.selected_item < len(self.view_items) - 1: self.selected_item = self.selected_item + 1 @@ -512,25 +503,6 @@ def get(self): return self.view_items[self.selected_item] return None - - def handle_key_press(self, key_pressed): - """Override base class function. - - UP_ARROW scrolls up, DOWN_ARROW scrolls down. - - Parameters - ---------- - key_pressed : int - key code of key pressed - """ - - super().handle_key_press(key_pressed) - if key_pressed == py_cui.keys.KEY_UP_ARROW: - self.scroll_up() - if key_pressed == py_cui.keys.KEY_DOWN_ARROW: - self.scroll_down() - - def draw(self): """Overrides base class draw function """ @@ -573,7 +545,22 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.selected_item_list = [] self.checked_char = checked_char self.set_focus_text('Focus mode on CheckBoxMenu. Use up/down to scroll, Enter to toggle set, unset, Esc to exit.') + self.key_map.bind(key=py_cui.keys.Key.ENTER, definition=self.select_item) + + def select_item(self, key: py_cui.keys.Key): + """Select a given item and set its view + Parameters + ---------- + key : Key + The key pressed to execute this event + """ + if super().get() in self.selected_item_list: + self.selected_item_list.remove(super().get()) + self.view_items[self.selected_item] = '[ ] - ' + self.view_items[self.selected_item][6:] + else: + self.view_items[self.selected_item] = '[{}] - '.format(self.checked_char) + self.view_items[self.selected_item][6:] + self.selected_item_list.append(self.view_items[self.selected_item]) def add_item(self, item_text): """Adds item to Checkbox @@ -635,29 +622,6 @@ def mark_item_as_checked(self, text): self.selected_item_list.remove(text) - def handle_key_press(self, key_pressed): - """Override of key presses. - - First, run the superclass function, scrolling should still work. - Adds Enter command to toggle selection - - Parameters - ---------- - key_pressed : int - key code of pressed key - """ - - super().handle_key_press(key_pressed) - if key_pressed == py_cui.keys.KEY_ENTER: - if super().get() in self.selected_item_list: - self.selected_item_list.remove(super().get()) - self.view_items[self.selected_item] = '[ ] - ' + self.view_items[self.selected_item][6:] - else: - self.view_items[self.selected_item] = '[{}] - '.format(self.checked_char) + self.view_items[self.selected_item][6:] - self.selected_item_list.append(self.view_items[self.selected_item]) - - - class Button(Widget): """Basic button widget. @@ -674,25 +638,21 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.command = command self.set_standard_color(py_cui.MAGENTA_ON_BLACK) self.set_focus_text('Focus mode on Button. Press Enter to press button, Esc to exit focus mode.') + self.key_map.bind_key(key=py_cui.keys.Key.ENTER, definition=self.button_action) - - def handle_key_press(self, key_pressed): - """Override of base class, adds ENTER listener that runs the button's command + def button_action(self, key: py_cui.keys.Key): + """Called when the button is pressed Parameters ---------- - key_pressed : int - Key code of pressed key + key : int + The key used to trigger this event """ - - super().handle_key_press(key_pressed) - if key_pressed == py_cui.keys.KEY_ENTER: - self.selected_color = py_cui.WHITE_ON_RED - if self.command is not None: - ret = self.command() - self.selected_color = py_cui.BLACK_ON_GREEN - return ret - + self.selected_color = py_cui.WHITE_ON_RED + if self.command is not None: + ret = self.command() + self.selected_color = py_cui.BLACK_ON_GREEN + return ret def draw(self): """Override of base class draw function @@ -744,7 +704,15 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.set_focus_text('Focus mode on TextBox. Press Esc to exit focus mode.') self.viewport_width = self.cursor_max_right - self.cursor_max_left self.password = password + self.key_map.bind_key(key=py_cui.keys.Key.LEFT_ARROW, definition=self.move_left) + self.key_map.bind_key(key=py_cui.keys.Key.RIGHT_ARROW, definition=self.move_right) + self.key_map.bind_key(key=py_cui.keys.Key.BACKSPACE, definition=self.erase_char) + self.key_map.bind_key(key=py_cui.keys.Key.DELETE, definition=self.delete_char) + self.key_map.bind_key(key=py_cui.keys.Key.HOME, definition=self.jump_to_start) + self.key_map.bind_key(key=py_cui.keys.Key.END, definition=self.jump_to_end) + for i in range(32, 128): + self.key_map.bind_key(key=py_cui.keys.Key(i), definition=self.insert_char) def update_height_width(self): """Need to update all cursor positions on resize @@ -815,7 +783,7 @@ def move_right(self): self.cursor_text_pos = self.cursor_text_pos + 1 - def insert_char(self, key_pressed): + def insert_char(self, key: py_cui.keys.Key): """Inserts char at cursor position. Internal use only @@ -831,7 +799,7 @@ def insert_char(self, key_pressed): self.cursor_text_pos = self.cursor_text_pos + 1 - def jump_to_start(self): + def jump_to_start(self, key: py_cui.keys.Key): """Jumps to the start of the textbox """ @@ -839,7 +807,7 @@ def jump_to_start(self): self.cursor_text_pos = 0 - def jump_to_end(self): + def jump_to_end(self, key: py_cui.keys.Key): """Jumps to the end to the textbox """ @@ -847,7 +815,7 @@ def jump_to_end(self): self.cursor_x = self.start_x + self.padx + 2 + self.cursor_text_pos - def erase_char(self): + def erase_char(self, key: py_cui.keys.Key): """Erases character at textbox cursor """ @@ -857,39 +825,13 @@ def erase_char(self): self.cursor_x = self.cursor_x - 1 self.cursor_text_pos = self.cursor_text_pos - 1 - def delete_char(self): + def delete_char(self, key: py_cui.keys.Key): """Deletes character to right of texbox cursor """ if self.cursor_text_pos < len(self.text): self.text = self.text[:self.cursor_text_pos] + self.text[self.cursor_text_pos + 1:] - def handle_key_press(self, key_pressed): - """Override of base handle key press function - - Parameters - ---------- - key_pressed : int - key code of key pressed - """ - - super().handle_key_press(key_pressed) - if key_pressed == py_cui.keys.KEY_LEFT_ARROW: - self.move_left() - elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: - self.move_right() - elif key_pressed == py_cui.keys.KEY_BACKSPACE: - self.erase_char() - elif key_pressed == py_cui.keys.KEY_DELETE: - self.delete_char() - elif key_pressed == py_cui.keys.KEY_HOME: - self.jump_to_start() - elif key_pressed == py_cui.keys.KEY_END: - self.jump_to_end() - elif key_pressed > 31 and key_pressed < 128: - self.insert_char(key_pressed) - - def draw(self): """Override of base draw function """ @@ -956,7 +898,20 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.cursor_max_right = self.start_x + self.width - padx - 1 self.viewport_width = self.cursor_max_right - self.cursor_max_left self.set_focus_text('Focus mode on TextBlock. Press Esc to exit focus mode.') - + + self.key_map.bind_key(key=py_cui.keys.Key.LEFT_ARROW, definition=self.move_left) + self.key_map.bind_key(key=py_cui.keys.Key.RIGHT_ARROW, definition=self.move_right) + self.key_map.bind_key(key=py_cui.keys.Key.UP_ARROW, definition=self.move_up) + self.key_map.bind_key(key=py_cui.keys.Key.DOWN_ARROW, definition=self.move_down) + self.key_map.bind_key(key=py_cui.keys.Key.BACKSPACE, definition=self.handle_backspace) + self.key_map.bind_key(key=py_cui.keys.Key.DELETE, definition=self.handle_delete) + self.key_map.bind_key(key=py_cui.keys.Key.ENTER, definition=self.handle_newline) + self.key_map.bind_key(key=py_cui.keys.Key.TAB, definition=lambda x: [self.insert_char(py_cui.keys.Key.SPACE) for _ in range(4)]) + self.key_map.bind_key(key=py_cui.keys.Key.HOME, definition=self.handle_home) + self.key_map.bind_key(key=py_cui.keys.Key.END, definition=self.handle_end) + + for i in range(32, 128): + self.key_map.bind_key(key=py_cui.keys.Key(i), definition=self.insert_char) def update_height_width(self): """Function that updates the position of the text and cursor on resize @@ -1066,10 +1021,9 @@ def set_text_line(self, text): self.text_lines[self.cursor_text_pos_y] = text - def move_left(self): + def move_left(self, key: py_cui.keys.Key): """Function that moves the cursor/text position one location to the left """ - if self.cursor_text_pos_x > 0: if self.cursor_x > self.cursor_max_left: self.cursor_x = self.cursor_x - 1 @@ -1078,7 +1032,7 @@ def move_left(self): self.cursor_text_pos_x = self.cursor_text_pos_x - 1 - def move_right(self): + def move_right(self, key: py_cui.keys.Key): """Function that moves the cursor/text position one location to the right """ @@ -1092,7 +1046,7 @@ def move_right(self): self.cursor_text_pos_x = self.cursor_text_pos_x + 1 - def move_up(self): + def move_up(self, key: py_cui.keys.Key): """Function that moves the cursor/text position one location up """ @@ -1108,10 +1062,9 @@ def move_up(self): self.cursor_text_pos_x = temp - def move_down(self): + def move_down(self, key: py_cui.keys.Key): """Function that moves the cursor/text position one location down """ - if self.cursor_text_pos_y < len(self.text_lines) - 1: if self.cursor_y < self.cursor_max_down: self.cursor_y = self.cursor_y + 1 @@ -1124,7 +1077,7 @@ def move_down(self): self.cursor_text_pos_x = temp - def handle_newline(self): + def handle_newline(self, key: py_cui.keys.Key): """Function that handles recieving newline characters in the text """ @@ -1144,7 +1097,7 @@ def handle_newline(self): self.viewport_y_start = self.viewport_y_start + 1 - def handle_backspace(self): + def handle_backspace(self, key: py_cui.keys.Key): """Function that handles recieving backspace characters in the text """ @@ -1167,7 +1120,7 @@ def handle_backspace(self): self.cursor_text_pos_x = self.cursor_text_pos_x - 1 - def handle_home(self): + def handle_home(self, key: py_cui.keys.Key): """Function that handles recieving a home keypress """ @@ -1176,7 +1129,7 @@ def handle_home(self): self.viewport_x_start = 0 - def handle_end(self): + def handle_end(self, key: py_cui.keys.Key): """Function that handles recieving an end keypress """ @@ -1190,7 +1143,7 @@ def handle_end(self): self.cursor_x = self.cursor_max_left + len(current_line) - def handle_delete(self): + def handle_delete(self, key: py_cui.keys.Key): """Function that handles recieving a delete keypress """ @@ -1221,43 +1174,6 @@ def insert_char(self, key_pressed): self.viewport_x_start = self.viewport_x_start + 1 self.cursor_text_pos_x = self.cursor_text_pos_x + 1 - - def handle_key_press(self, key_pressed): - """Override of base class handle key press function - - Parameters - ---------- - key_pressed : int - key code of key pressed - """ - - super().handle_key_press(key_pressed) - - if key_pressed == py_cui.keys.KEY_LEFT_ARROW: - self.move_left() - elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: - self.move_right() - elif key_pressed == py_cui.keys.KEY_UP_ARROW: - self.move_up() - elif key_pressed == py_cui.keys.KEY_DOWN_ARROW and self.cursor_text_pos_y < len(self.text_lines) - 1: - self.move_down() - elif key_pressed == py_cui.keys.KEY_BACKSPACE: - self.handle_backspace() - elif key_pressed == py_cui.keys.KEY_DELETE: - self.handle_delete() - elif key_pressed == py_cui.keys.KEY_ENTER: - self.handle_newline() - elif key_pressed == py_cui.keys.KEY_TAB: - for _ in range(0, 4): - self.insert_char(py_cui.keys.KEY_SPACE) - elif key_pressed == py_cui.keys.KEY_HOME: - self.handle_home() - elif key_pressed == py_cui.keys.KEY_END: - self.handle_end() - elif key_pressed > 31 and key_pressed < 128: - self.insert_char(key_pressed) - - def draw(self): """Override of base class draw function """ From 832db97b47ab1f10274f1b6268282fee1bf9d994 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 00:41:46 -0500 Subject: [PATCH 07/20] Moved popups to key map --- py_cui/popups.py | 209 ++++++++++++++--------------------------------- 1 file changed, 63 insertions(+), 146 deletions(-) diff --git a/py_cui/popups.py b/py_cui/popups.py index 7015b94..1fa44cd 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -60,8 +60,8 @@ def __init__(self, root, title, text, color, renderer): self.pady = 0 self.renderer = renderer self.selected = True - self.close_keys = [py_cui.keys.KEY_ESCAPE] - + self.key_map = py_cui.keys.KeyMap() + self.key_map.bind_key(key=py_cui.keys.Key.ESCAPE, lambda x: self.root.close_popup()) def handle_key_press(self, key_pressed): """Handles key presses when popup is open @@ -73,10 +73,7 @@ def handle_key_press(self, key_pressed): key_pressed : int The ascii code for the key that was pressed """ - - if key_pressed in self.close_keys: - self.root.close_popup() - + self.key_map.execute(key_pressed) def draw(self): """Function that uses renderer to draw the popup @@ -111,7 +108,7 @@ def __init__(self, root, title, text, color, renderer): """ super().__init__(root, title, text, color, renderer) - self.close_keys = [py_cui.keys.KEY_ENTER, py_cui.keys.KEY_ESCAPE, py_cui.keys.KEY_SPACE, py_cui.keys.KEY_BACKSPACE, py_cui.keys.KEY_DELETE] + self.close_keys = [py_cui.keys.Key.ENTER, py_cui.keys.Key.ESCAPE, py_cui.keys.Key.SPACE, py_cui.keys.Key.BACKSPACE, py_cui.keys.Key.DELETE] def draw(self): @@ -136,33 +133,23 @@ def __init__(self, root, title, text, color, command, renderer): super().__init__(root, title, text, color, renderer) self.command = command + self.key_map.bind_key(key=py_cui.keys.key.Y_LOWER, definition=self.positive_answer) + self.key_map.bind_key(key=py_cui.keys.key.Y_UPPER, definition=self.positive_answer) + self.key_map.bind_key(key=py_cui.keys.key.N_LOWER, definition=self.negative_answer) + self.key_map.bind_key(key=py_cui.keys.key.N_UPPER, definition=self.negative_answer) - def handle_key_press(self, key_pressed): - """Handle key press overwrite from superclass - - Parameters - ---------- - key_pressed : int - key code of key pressed - """ - - super().handle_key_press(key_pressed) - valid_pressed = False - if key_pressed == py_cui.keys.KEY_Y_LOWER or key_pressed == py_cui.keys.KEY_Y_UPPER: - self.ret_val = True - valid_pressed = True - elif key_pressed == py_cui.keys.KEY_N_UPPER or key_pressed == py_cui.keys.KEY_N_LOWER: - self.ret_val = False - valid_pressed = True - - if valid_pressed: - self.root.close_popup() - if self.command is not None: - self.command(self.ret_val) - else: - self.root.show_warning_popup('No Command Specified', 'The Yes/No popup had no specified command') + def positive_answer(self, key: py_cui.keys.Key): + if self.command: + self.command(True) + else: + self.root.show_warning_popup('No Command Specified', 'The Yes/No popup had no specified command') + def negative_answer(self, key: py_cui.keys.Key): + if self.command: + self.command(False) + else: + self.root.show_warning_popup('No Command Specified', 'The Yes/No popup had no specified command') def draw(self): """Uses base class draw function @@ -203,7 +190,23 @@ def __init__(self, root, title, color, command, renderer, password): self.cursor_y = self.start_y + int(self.height / 2) + 1 self.viewport_width = self.cursor_max_right - self.cursor_max_left self.password = password - + self.key_map.bind_key(key=py_cui.keys.Key.ENTER, definition=self.return_input) + self.key_map.bind_key(key=py_cui.keys.Key.LEFT_ARROW, definition=self.move_left) + self.key_map.bind_key(key=py_cui.keys.Key.RIGHT_ARROW, definition=self.move_right) + self.key_map.bind_key(key=py_cui.keys.Key.BACKSPACE, definition=self.erase_char) + self.key_map.bind_key(key=py_cui.keys.Key.DELETE, definition=self.delete_char) + self.key_map.bind_key(key=py_cui.keys.Key.HOME, definition=self.jump_to_start) + self.key_map.bind_key(key=py_cui.keys.Key.END, definition=self.jump_to_end) + + for i in range(32, 128): + self.key_map.bind_key(key=py_cui.keys.Key(i), definition=self.insert_char) + + def return_input(self, key: py_cui.keys.Key): + self.root.close_popup() + if self.command is not None: + self.command(self.ret_val) + else: + self.root.show_warning_popup('No Command Specified', 'The Yes/No popup had no specified command') def set_text(self, text): """Sets the value of the text. Overwrites existing text @@ -242,7 +245,7 @@ def clear(self): self.text = '' - def move_left(self): + def move_left(self, key: py_cui.keys.Key): """Shifts the cursor the the left. Internal use only """ @@ -252,7 +255,7 @@ def move_left(self): self.cursor_text_pos = self.cursor_text_pos - 1 - def move_right(self): + def move_right(self, key: py_cui.keys.Key): """Shifts the cursor the the right. Internal use only """ @@ -262,7 +265,7 @@ def move_right(self): self.cursor_text_pos = self.cursor_text_pos + 1 - def insert_char(self, key_pressed): + def insert_char(self, key: py_cui.keys.Key): """Inserts char at cursor position. Internal use only Parameters @@ -277,7 +280,7 @@ def insert_char(self, key_pressed): self.cursor_text_pos = self.cursor_text_pos + 1 - def jump_to_start(self): + def jump_to_start(self, key: py_cui.keys.Key): """Jumps to the start of the textbox """ @@ -285,7 +288,7 @@ def jump_to_start(self): self.cursor_text_pos = 0 - def jump_to_end(self): + def jump_to_end(self, key: py_cui.keys.Key): """Jumps to the end to the textbox """ @@ -293,7 +296,7 @@ def jump_to_end(self): self.cursor_x = self.start_x + self.padx + 2 + self.cursor_text_pos - def erase_char(self): + def erase_char(self, key: py_cui.keys.Key): """Erases character at textbox cursor """ @@ -304,53 +307,13 @@ def erase_char(self): self.cursor_text_pos = self.cursor_text_pos - 1 - def delete_char(self): + def delete_char(self, key: py_cui.keys.Key): """Deletes character to right of texbox cursor """ if self.cursor_text_pos < len(self.text): self.text = self.text[:self.cursor_text_pos] + self.text[self.cursor_text_pos + 1:] - - - def handle_key_press(self, key_pressed): - """Override of base handle key press function - - Parameters - ---------- - key_pressed : int - key code of key pressed - """ - - super().handle_key_press(key_pressed) - valid_pressed = False - if key_pressed == py_cui.keys.KEY_ENTER: - self.ret_val = self.text - valid_pressed = True - - if valid_pressed: - self.root.close_popup() - if self.command is not None: - self.command(self.ret_val) - else: - self.root.show_warning_popup('No Command Specified', 'The Yes/No popup had no specified command') - - if key_pressed == py_cui.keys.KEY_LEFT_ARROW: - self.move_left() - elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: - self.move_right() - elif key_pressed == py_cui.keys.KEY_BACKSPACE: - self.erase_char() - elif key_pressed == py_cui.keys.KEY_DELETE: - self.delete_char() - elif key_pressed == py_cui.keys.KEY_HOME: - self.jump_to_start() - elif key_pressed == py_cui.keys.KEY_END: - self.jump_to_end() - elif key_pressed > 31 and key_pressed < 128: - self.insert_char(key_pressed) - - def draw(self): """Override of base draw function """ @@ -407,9 +370,28 @@ def __init__(self, root, items, title, color, command, renderer, run_command_if_ self.view_items = items self.command = command self.run_command_if_none = run_command_if_none + self.key_map.bind_key(key=py_cui.keys.Key.ENTER, definition=self.choose_value) + self.key_map.bind_key(key=py_cui.keys.Key.UP_ARROW, definition=self.scroll_up) + self.key_map.bind_key(key=py_cui.keys.Key.DOWN_ARROW, definition=self.scroll_down) + def choose_value(self, key: py_cui.keys.Key): + valid_pressed = False + if key == py_cui.keys.Key.ENTER: + self.ret_val = self.get() + valid_pressed = True + elif key == py_cui.keys.Key.ESCAPE: + self.ret_val = None + valid_pressed = True - def scroll_up(self): + if valid_pressed: + self.root.close_popup() + if self.command is not None: + if self.ret_val is not None or self.run_command_if_none: + self.command(self.ret_val) + else: + self.root.show_warning_popup('No Command Specified', 'The menu popup had no specified command') + + def scroll_up(self, key: py_cui.keys.Key): """Function that scrolls the view up in the scroll menu """ @@ -420,7 +402,7 @@ def scroll_up(self): self.selected_item = self.selected_item - 1 - def scroll_down(self): + def scroll_down(self, key: py_cui.keys.Key): """Function that scrolls the view down in the scroll menu """ @@ -444,41 +426,6 @@ def get(self): return self.view_items[self.selected_item] return None - - def handle_key_press(self, key_pressed): - """Override of base handle key press function - - Enter key runs command, Escape key closes menu - - Parameters - ---------- - key_pressed : int - key code of key pressed - """ - - super().handle_key_press(key_pressed) - valid_pressed = False - if key_pressed == py_cui.keys.KEY_ENTER: - self.ret_val = self.get() - valid_pressed = True - elif key_pressed == py_cui.keys.KEY_ESCAPE: - self.ret_val = None - valid_pressed = True - - if valid_pressed: - self.root.close_popup() - if self.command is not None: - if self.ret_val is not None or self.run_command_if_none: - self.command(self.ret_val) - else: - self.root.show_warning_popup('No Command Specified', 'The menu popup had no specified command') - - if key_pressed == py_cui.keys.KEY_UP_ARROW: - self.scroll_up() - if key_pressed == py_cui.keys.KEY_DOWN_ARROW: - self.scroll_down() - - def draw(self): """Overrides base class draw function """ @@ -528,21 +475,6 @@ def __init__(self, root, title, message, color, renderer): self.icon_counter = 0 self.message = message - - def handle_key_press(self, key_pressed): - """Override of base class function. - - Loading icon popups cannot be cancelled, so we wish to avoid default behavior - - Parameters - ---------- - key_pressed : int - key code of pressed key - """ - - pass - - def draw(self): """Overrides base draw function """ @@ -575,21 +507,6 @@ def __init__(self, root, title, num_items, color, renderer): self.num_items = num_items self.completed_items = 0 - - def handle_key_press(self, key_pressed): - """Override of base class function. - - Loading icon popups cannot be cancelled, so we wish to avoid default behavior - - Parameters - ---------- - key_pressed : int - key code of pressed key - """ - - pass - - def draw(self): """Override of base draw function """ From dc909638a06797bcac49aec342fd4da4cdc02db3 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 00:43:52 -0500 Subject: [PATCH 08/20] Fixed syntax error --- py_cui/popups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py_cui/popups.py b/py_cui/popups.py index 1fa44cd..7b3adf3 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -61,7 +61,7 @@ def __init__(self, root, title, text, color, renderer): self.renderer = renderer self.selected = True self.key_map = py_cui.keys.KeyMap() - self.key_map.bind_key(key=py_cui.keys.Key.ESCAPE, lambda x: self.root.close_popup()) + self.key_map.bind_key(key=py_cui.keys.Key.ESCAPE, definition=lambda x: self.root.close_popup()) def handle_key_press(self, key_pressed): """Handles key presses when popup is open From 329c91f2aebd9878dc4c5db4f4b6375eaf73a2a6 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 01:53:09 -0500 Subject: [PATCH 09/20] Finished basic implementation of key_map --- py_cui/__init__.py | 23 +++++++++-------------- py_cui/keys.py | 17 +++++++++++++++-- py_cui/popups.py | 14 +++++++++----- py_cui/widget_set.py | 2 +- py_cui/widgets.py | 31 +++++++++++++++++++++---------- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 5f121b8..e548077 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -705,12 +705,11 @@ def lose_focus(self): After popup is called, focus is lost """ - if self.in_focused_mode: self.in_focused_mode = False self.status_bar.set_text(self.init_status_bar_text) self.widgets[self.selected_widget].selected = False - + self.widgets[self.selected_widget].on_lose_focus = None def move_focus(self, widget): """Moves focus mode to different widget @@ -726,6 +725,7 @@ def move_focus(self, widget): widget.selected = True self.in_focused_mode = True self.status_bar.set_text(widget.get_help_text()) + widget.on_lose_focus = self.lose_focus # Popup functions. Used to display messages, warnings, and errors to the user. @@ -1003,6 +1003,7 @@ def select_widget(self, key: py_cui.keys.Key): if self.popup is None and self.selected_widget is not None and selected_widget.is_selectable: self.in_focused_mode = True selected_widget.selected = True + selected_widget.on_lose_focus = self.lose_focus # If autofocus buttons is selected, we automatically process the button command and reset to overview mode if self.auto_focus_buttons and isinstance(selected_widget, widgets.Button): self.in_focused_mode = False @@ -1035,7 +1036,6 @@ def handle_key_presses(self, key_pressed): key_pressed : py_cui.keys.Key.* The key being pressed """ - print(key_pressed.value, key_pressed.name) # Selected widget represents which widget is being hovered over, though not necessarily in focus mode if self.selected_widget is None: return @@ -1043,15 +1043,15 @@ def handle_key_presses(self, key_pressed): # Otherwise, barring a popup, we are in overview mode, meaning that arrow py_cui.keys move between widgets, and Enter key starts focus mode if not self.popup and self.selected_widget and self.in_focused_mode: self.widgets[self.selected_widget].handle_key_press(key_pressed) - print('processed widget handler') # if we have a popup, that takes key control from both overview and focus mode elif self.popup is not None: - print('popup handler') self.popup.handle_key_press(key_pressed) else: - print('executing main keymap') - self.key_map.execute(key_pressed) - + try: + key = py_cui.keys.Key(key_pressed) + self.key_map.execute(key) + except ValueError: + return def draw(self, stdscr): """Main CUI draw loop called by start() @@ -1100,12 +1100,7 @@ def draw(self, stdscr): self.post_loading_callback = None # Handle keypresses - if key_pressed != 0: - try: - key = py_cui.keys.Key(key_pressed) - self.handle_key_presses(key) - except ValueError as e: - continue + self.handle_key_presses(key_pressed) # Draw status/title bar, and all widgets. Selected widget will be bolded. self.draw_status_bars(stdscr, height, width) diff --git a/py_cui/keys.py b/py_cui/keys.py index 42950d9..e00fde7 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -149,7 +149,8 @@ def bind_key(self, /, key: Key, *, definition: Callable=None, old: Key=None): raise ValueError(f"Cannot bind to both a key and a callable") if old: self._bindings[key.value] = self._bindings[old.value] - self._bindings[key.value] = definition + else: + self._bindings[key.value] = (definition, key) def execute(self, key: Key): if not isinstance(key, Key): @@ -157,7 +158,7 @@ def execute(self, key: Key): elif key.value not in self._bindings.keys(): return - self._bindings[key.value](key) + self._bindings[key.value][0](self._bindings[key.value][1]) def unbind(self, key: Key): """Unbinds a key from the map @@ -177,3 +178,15 @@ def __add__(self, value): k = KeyMap() k._bindings = {**value._binings, **self._bindings} return k + +class RawKeyMap(object): + def __init__(self, char_range: range): + self.char_range = char_range + self.definition = None + + def add_definition(self, definition): + self.definition = definition + + def execute(self, key: int): + if key in self.char_range and self.definition: + self.definition(key) diff --git a/py_cui/popups.py b/py_cui/popups.py index 7b3adf3..db82218 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -63,7 +63,7 @@ def __init__(self, root, title, text, color, renderer): self.key_map = py_cui.keys.KeyMap() self.key_map.bind_key(key=py_cui.keys.Key.ESCAPE, definition=lambda x: self.root.close_popup()) - def handle_key_press(self, key_pressed): + def handle_key_press(self, key_pressed: int): """Handles key presses when popup is open By default, only closes popup when Escape is pressed @@ -73,7 +73,12 @@ def handle_key_press(self, key_pressed): key_pressed : int The ascii code for the key that was pressed """ - self.key_map.execute(key_pressed) + try: + self.key_map.execute(key_pressed) + key = py_cui.keys.Key(key_pressed) + self.raw_key_map.execute(key) + except ValueError: + return def draw(self): """Function that uses renderer to draw the popup @@ -198,8 +203,7 @@ def __init__(self, root, title, color, command, renderer, password): self.key_map.bind_key(key=py_cui.keys.Key.HOME, definition=self.jump_to_start) self.key_map.bind_key(key=py_cui.keys.Key.END, definition=self.jump_to_end) - for i in range(32, 128): - self.key_map.bind_key(key=py_cui.keys.Key(i), definition=self.insert_char) + self.raw_key_map.add_definition(self.insert_char) def return_input(self, key: py_cui.keys.Key): self.root.close_popup() @@ -265,7 +269,7 @@ def move_right(self, key: py_cui.keys.Key): self.cursor_text_pos = self.cursor_text_pos + 1 - def insert_char(self, key: py_cui.keys.Key): + def insert_char(self, key: int): """Inserts char at cursor position. Internal use only Parameters diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 599c9d0..eb525bf 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -63,7 +63,7 @@ def add_key_command(self, key, command): Parameters ---------- - key : py_cui.keys.KEY_* + key : py_cui.keys.Key.* The key bound to the command command : Function A no-arg or lambda function to fire on keypress diff --git a/py_cui/widgets.py b/py_cui/widgets.py index cfb5d0e..5d0426c 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -24,6 +24,7 @@ import py_cui import py_cui.colors import py_cui.errors +import py_cui.keys class Widget: @@ -66,12 +67,14 @@ class Widget: Dictionary mapping key codes to functions text_color_rules : list of py_cui.ColorRule color rules to load into renderer when drawing widget + on_lose_focus : Callable + called when this widget loses focus """ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, selectable = True): """Constructor for base widget class """ - + self.on_lose_focus = None if grid is None: raise py_cui.errors.PyCUIMissingParentError("Cannot add widget to NoneType") self.id = id @@ -95,7 +98,12 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.help_text = 'No help text available.' self.text_color_rules = [] self.key_map = py_cui.keys.KeyMap() + self.key_map.bind_key(key=py_cui.keys.Key.ESCAPE, definition=self._on_lose_focus) + self.raw_key_map = py_cui.keys.RawKeyMap(range(32, 128)) + def _on_lose_focus(self, key: py_cui.keys.Key): + if self.on_lose_focus: + self.on_lose_focus() def set_focus_text(self, text): """Function that sets the text of the status bar on focus for a particular widget @@ -276,7 +284,12 @@ def handle_key_press(self, key_pressed): key_pressed : int key code of key pressed """ - self.key_map.execute(key_pressed) + try: + self.raw_key_map.execute(key_pressed) + key = py_cui.keys.Key(key_pressed) + self.key_map.execute(key) + except ValueError: + return def draw(self): """Base class draw class that checks if renderer is valid. @@ -710,9 +723,8 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.key_map.bind_key(key=py_cui.keys.Key.DELETE, definition=self.delete_char) self.key_map.bind_key(key=py_cui.keys.Key.HOME, definition=self.jump_to_start) self.key_map.bind_key(key=py_cui.keys.Key.END, definition=self.jump_to_end) - - for i in range(32, 128): - self.key_map.bind_key(key=py_cui.keys.Key(i), definition=self.insert_char) + + self.raw_key_map.add_definition(self.insert_char) def update_height_width(self): """Need to update all cursor positions on resize @@ -783,7 +795,7 @@ def move_right(self): self.cursor_text_pos = self.cursor_text_pos + 1 - def insert_char(self, key: py_cui.keys.Key): + def insert_char(self, key: int): """Inserts char at cursor position. Internal use only @@ -793,7 +805,7 @@ def insert_char(self, key: py_cui.keys.Key): key_pressed : int key code of key pressed """ - self.text = self.text[:self.cursor_text_pos] + chr(key_pressed) + self.text[self.cursor_text_pos:] + self.text = self.text[:self.cursor_text_pos] + chr(key) + self.text[self.cursor_text_pos:] if len(self.text) < self.viewport_width: self.cursor_x = self.cursor_x + 1 self.cursor_text_pos = self.cursor_text_pos + 1 @@ -910,8 +922,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.key_map.bind_key(key=py_cui.keys.Key.HOME, definition=self.handle_home) self.key_map.bind_key(key=py_cui.keys.Key.END, definition=self.handle_end) - for i in range(32, 128): - self.key_map.bind_key(key=py_cui.keys.Key(i), definition=self.insert_char) + self.raw_key_map.add_definition(self.insert_char) def update_height_width(self): """Function that updates the position of the text and cursor on resize @@ -1156,7 +1167,7 @@ def handle_delete(self, key: py_cui.keys.Key): self.set_text_line(current_line[:self.cursor_text_pos_x] + current_line[self.cursor_text_pos_x+1:]) - def insert_char(self, key_pressed): + def insert_char(self, key: int): """Function that handles recieving a character Parameters From ab1cffc4e889810ae4aea8ecffbf10e2e9f45321 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 02:24:11 -0500 Subject: [PATCH 10/20] Updated authors --- AUTHORS | 2 +- py_cui/keys.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 82eedd3..edea191 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,6 +3,6 @@ Primary Author(s): Contributor(s): Maciej Wlodek - telday + Ellis Wright (telday) Aaron Pierce Caleb Reese (cptbldbrd) diff --git a/py_cui/keys.py b/py_cui/keys.py index e00fde7..ce686db 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -1,6 +1,6 @@ """Module containing constants and helper functions for dealing with keys. -@author: Jakub Wlodek +@author: Jakub Wlodek, Ellis Wright (telday) @created: 12-Aug-2019 """ From 226ed829e24a2cfb071188a938198bc408a4063c Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 13:09:26 -0500 Subject: [PATCH 11/20] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1e47e41..909caed 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ dist/ notes/ markdoc/ npdoc2md/ -*.*~ +*~ epy_cui/ activate.bat *.swp From 576e34b036842bc6c29289752e04c82bbe2a2c28 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 13:15:25 -0500 Subject: [PATCH 12/20] Updated docs --- py_cui/keys.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/py_cui/keys.py b/py_cui/keys.py index ce686db..3f1069f 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -44,6 +44,7 @@ def get_char_from_ascii(key_num): class Key(enum.Enum): + """Enum representing a Key internally""" # KeysSupported py_cui keys ENTER = get_ascii_from_char('\n') #Esccape character is ascii #27 @@ -124,6 +125,13 @@ class Key(enum.Enum): class KeyMap(object): + """Represents a map of keys to functionality internally + + Attributes + ---------- + _bindings : dict + The list of bindings + """ def __init__(self): self._bindings = dict() @@ -153,6 +161,13 @@ def bind_key(self, /, key: Key, *, definition: Callable=None, old: Key=None): self._bindings[key.value] = (definition, key) def execute(self, key: Key): + """Execute the given key on this map + + Parameters + ---------- + key : Key + The key to execute + """ if not isinstance(key, Key): raise ValueError(f"{key} is an invalid value for key") elif key.value not in self._bindings.keys(): @@ -180,13 +195,38 @@ def __add__(self, value): return k class RawKeyMap(object): + """ + A keymap for mapping large amounts of characters to + a single action/definition + + Attributes + ---------- + char_range : range + The range of characters to map + definition : Callable + The function to call on execution + """ def __init__(self, char_range: range): self.char_range = char_range self.definition = None def add_definition(self, definition): + """Add a definition/function to this RawKeyMap + + Parameters + ---------- + definition : Callable + The definition to add + """ self.definition = definition def execute(self, key: int): + """Execute this keymap on the given key + + Parameters + ---------- + key : int + The key to execute + """ if key in self.char_range and self.definition: self.definition(key) From 23e9e0579d38b6152d0cfd3dd9a60f2f9b264849 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 13:35:11 -0500 Subject: [PATCH 13/20] Changed tests to import new key enum --- tests/test_text_block.py | 6 +++--- tests/test_text_box.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_text_block.py b/tests/test_text_block.py index 6e46c4f..4141ea0 100644 --- a/tests/test_text_block.py +++ b/tests/test_text_block.py @@ -44,7 +44,7 @@ def test_get_initial(): def test_insert_char(): text_box = py_cui.widgets.ScrollTextBlock('id', 'Test', grid_test, 1, 1, 1, 2, 1, 0 , 'Hello World') - text_box.insert_char(py_cui.keys.KEY_D_UPPER) + text_box.insert_char(py_cui.keys.Key.D_UPPER.value) assert text_box.get_current_line() == 'DHello World' assert text_box.cursor_x == text_box.cursor_max_left + 1 assert text_box.cursor_text_pos_x == 1 @@ -74,8 +74,8 @@ def test_get_edited(): for i in range(0, 3): text_box.move_right() text_box.handle_backspace() - text_box.insert_char(py_cui.keys.KEY_A_LOWER) - text_box.insert_char(py_cui.keys.KEY_E_UPPER) + text_box.insert_char(py_cui.keys.Key.A_LOWER.value) + text_box.insert_char(py_cui.keys.Key.E_UPPER.value) assert text_box.cursor_x == text_box.cursor_max_left + 4 assert text_box.cursor_text_pos_x == 4 assert text_box.get_current_line() == 'HeaElo World' diff --git a/tests/test_text_box.py b/tests/test_text_box.py index b5f623b..00842ec 100644 --- a/tests/test_text_box.py +++ b/tests/test_text_box.py @@ -40,7 +40,7 @@ def test_get_initial(): def test_insert_char(): text_box = py_cui.widgets.TextBox('id', 'Test', grid_test, 1, 1, 1, 2, 1, 0 , 'Hello World', False) - text_box.insert_char(py_cui.keys.KEY_D_UPPER) + text_box.insert_char(py_cui.keys.Key.D_UPPER.value) assert text_box.get() == 'DHello World' assert text_box.cursor_x == text_box.cursor_max_left + 1 assert text_box.cursor_text_pos == 1 @@ -61,8 +61,8 @@ def test_get_edited(): for i in range(0, 3): text_box.move_right() text_box.erase_char() - text_box.insert_char(py_cui.keys.KEY_A_LOWER) - text_box.insert_char(py_cui.keys.KEY_E_UPPER) + text_box.insert_char(py_cui.keys.Key.A_LOWER.value) + text_box.insert_char(py_cui.keys.Key.E_UPPER.value) assert text_box.cursor_x == text_box.cursor_max_left + 4 assert text_box.cursor_text_pos == 4 assert text_box.get() == 'HeaElo World' From a679621a5a6c325d8d019f50d4c1a91e4f03ddaa Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 13:47:24 -0500 Subject: [PATCH 14/20] Added decorator to let methods which dont need to know their key ignore --- py_cui/keys.py | 4 ++ py_cui/widgets.py | 114 +++++++++++++++++++++++++--------------------- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/py_cui/keys.py b/py_cui/keys.py index 3f1069f..6d5f171 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -42,6 +42,10 @@ def get_char_from_ascii(key_num): """ return chr(key_num) +def ignore_key(method): + def wrapper(self, key=None, *args, **kwargs): + return method(self) + return wrapper class Key(enum.Enum): """Enum representing a Key internally""" diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 5d0426c..aa62334 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -102,9 +102,28 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.raw_key_map = py_cui.keys.RawKeyMap(range(32, 128)) def _on_lose_focus(self, key: py_cui.keys.Key): + """Called on this widget losing focus + + Parameters + ---------- + key : Key + The key pressed to trigger this event + """ if self.on_lose_focus: self.on_lose_focus() + def add_key_command(self, key, command): + """Maps a keycode to a function that will be executed when in focus mode + + Parameters + ---------- + key : py_cui.keys.KEY + ascii keycode used to map the key + command : function without args + a non-argument function or lambda function to execute if in focus mode and key is pressed + """ + self.key_map.bind_key(key=key, definition=lambda x: command()) + def set_focus_text(self, text): """Function that sets the text of the status bar on focus for a particular widget @@ -424,14 +443,9 @@ def clear(self): self.top_view = 0 - def scroll_up(self, key: py_cui.keys.Key): - """Function that scrolls the view up in the scroll menu - - Parameters - ---------- - key : Key - The key pressed to trigger this event - """ + @py_cui.keys.ignore_key + def scroll_up(self): + """Function that scrolls the view up in the scroll menu""" if self.selected: if self.top_view > 0 and self.selected_item == self.top_view: @@ -440,14 +454,9 @@ def scroll_up(self, key: py_cui.keys.Key): self.selected_item = self.selected_item - 1 - def scroll_down(self, key: py_cui.keys.Key): - """Function that scrolls the view down in the scroll menu - - Parameters - ---------- - key : Key - The key pressed to trigger this event - """ + @py_cui.keys.ignore_key + def scroll_down(self): + """Function that scrolls the view down in the scroll menu""" if self.selected: if self.selected_item < len(self.view_items) - 1: self.selected_item = self.selected_item + 1 @@ -559,15 +568,10 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.checked_char = checked_char self.set_focus_text('Focus mode on CheckBoxMenu. Use up/down to scroll, Enter to toggle set, unset, Esc to exit.') self.key_map.bind(key=py_cui.keys.Key.ENTER, definition=self.select_item) - - def select_item(self, key: py_cui.keys.Key): - """Select a given item and set its view - - Parameters - ---------- - key : Key - The key pressed to execute this event - """ + + @py_cui.keys.ignore_key + def select_item(self): + """Select a given item and set its view""" if super().get() in self.selected_item_list: self.selected_item_list.remove(super().get()) self.view_items[self.selected_item] = '[ ] - ' + self.view_items[self.selected_item][6:] @@ -653,14 +657,9 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.set_focus_text('Focus mode on Button. Press Enter to press button, Esc to exit focus mode.') self.key_map.bind_key(key=py_cui.keys.Key.ENTER, definition=self.button_action) - def button_action(self, key: py_cui.keys.Key): - """Called when the button is pressed - - Parameters - ---------- - key : int - The key used to trigger this event - """ + @py_cui.keys.ignore_key + def button_action(self): + """Called when the button is pressed""" self.selected_color = py_cui.WHITE_ON_RED if self.command is not None: ret = self.command() @@ -775,7 +774,7 @@ def clear(self): self.cursor_text_pos = 0 self.text = '' - + @py_cui.keys.ignore_key def move_left(self): """Shifts the cursor the the left. Internal use only """ @@ -786,6 +785,7 @@ def move_left(self): self.cursor_text_pos = self.cursor_text_pos - 1 + @py_cui.keys.ignore_key def move_right(self): """Shifts the cursor the the right. Internal use only """ @@ -810,16 +810,16 @@ def insert_char(self, key: int): self.cursor_x = self.cursor_x + 1 self.cursor_text_pos = self.cursor_text_pos + 1 - - def jump_to_start(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def jump_to_start(self): """Jumps to the start of the textbox """ self.cursor_x = self.start_x + self.padx + 2 self.cursor_text_pos = 0 - - def jump_to_end(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def jump_to_end(self): """Jumps to the end to the textbox """ @@ -827,7 +827,8 @@ def jump_to_end(self, key: py_cui.keys.Key): self.cursor_x = self.start_x + self.padx + 2 + self.cursor_text_pos - def erase_char(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def erase_char(self): """Erases character at textbox cursor """ @@ -837,7 +838,8 @@ def erase_char(self, key: py_cui.keys.Key): self.cursor_x = self.cursor_x - 1 self.cursor_text_pos = self.cursor_text_pos - 1 - def delete_char(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def delete_char(self): """Deletes character to right of texbox cursor """ @@ -918,7 +920,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.key_map.bind_key(key=py_cui.keys.Key.BACKSPACE, definition=self.handle_backspace) self.key_map.bind_key(key=py_cui.keys.Key.DELETE, definition=self.handle_delete) self.key_map.bind_key(key=py_cui.keys.Key.ENTER, definition=self.handle_newline) - self.key_map.bind_key(key=py_cui.keys.Key.TAB, definition=lambda x: [self.insert_char(py_cui.keys.Key.SPACE) for _ in range(4)]) + self.key_map.bind_key(key=py_cui.keys.Key.TAB, definition=lambda x: [self.insert_char(py_cui.keys.Key.SPACE.value) for _ in range(4)]) self.key_map.bind_key(key=py_cui.keys.Key.HOME, definition=self.handle_home) self.key_map.bind_key(key=py_cui.keys.Key.END, definition=self.handle_end) @@ -1032,7 +1034,8 @@ def set_text_line(self, text): self.text_lines[self.cursor_text_pos_y] = text - def move_left(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def move_left(self): """Function that moves the cursor/text position one location to the left """ if self.cursor_text_pos_x > 0: @@ -1043,7 +1046,8 @@ def move_left(self, key: py_cui.keys.Key): self.cursor_text_pos_x = self.cursor_text_pos_x - 1 - def move_right(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def move_right(self): """Function that moves the cursor/text position one location to the right """ @@ -1057,7 +1061,8 @@ def move_right(self, key: py_cui.keys.Key): self.cursor_text_pos_x = self.cursor_text_pos_x + 1 - def move_up(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def move_up(self): """Function that moves the cursor/text position one location up """ @@ -1073,7 +1078,8 @@ def move_up(self, key: py_cui.keys.Key): self.cursor_text_pos_x = temp - def move_down(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def move_down(self): """Function that moves the cursor/text position one location down """ if self.cursor_text_pos_y < len(self.text_lines) - 1: @@ -1088,7 +1094,8 @@ def move_down(self, key: py_cui.keys.Key): self.cursor_text_pos_x = temp - def handle_newline(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def handle_newline(self): """Function that handles recieving newline characters in the text """ @@ -1108,7 +1115,8 @@ def handle_newline(self, key: py_cui.keys.Key): self.viewport_y_start = self.viewport_y_start + 1 - def handle_backspace(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def handle_backspace(self): """Function that handles recieving backspace characters in the text """ @@ -1130,8 +1138,8 @@ def handle_backspace(self, key: py_cui.keys.Key): self.cursor_x = self.cursor_x - 1 self.cursor_text_pos_x = self.cursor_text_pos_x - 1 - - def handle_home(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def handle_home(self): """Function that handles recieving a home keypress """ @@ -1140,7 +1148,8 @@ def handle_home(self, key: py_cui.keys.Key): self.viewport_x_start = 0 - def handle_end(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def handle_end(self): """Function that handles recieving an end keypress """ @@ -1154,7 +1163,8 @@ def handle_end(self, key: py_cui.keys.Key): self.cursor_x = self.cursor_max_left + len(current_line) - def handle_delete(self, key: py_cui.keys.Key): + @py_cui.keys.ignore_key + def handle_delete(self): """Function that handles recieving a delete keypress """ @@ -1178,7 +1188,7 @@ def insert_char(self, key: int): current_line = self.get_current_line() - self.set_text_line(current_line[:self.cursor_text_pos_x] + chr(key_pressed) + current_line[self.cursor_text_pos_x:]) + self.set_text_line(current_line[:self.cursor_text_pos_x] + chr(key) + current_line[self.cursor_text_pos_x:]) if len(current_line) <= self.width - 2 * self.padx - 4: self.cursor_x = self.cursor_x + 1 elif self.viewport_x_start + self.width - 2 * self.padx - 4 < len(current_line): From 59921e52c12370f72081e160cddbc4b6caf5d7ef Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 13:50:04 -0500 Subject: [PATCH 15/20] Downgraded method call --- py_cui/keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py_cui/keys.py b/py_cui/keys.py index 6d5f171..d0a1661 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -139,7 +139,7 @@ class KeyMap(object): def __init__(self): self._bindings = dict() - def bind_key(self, /, key: Key, *, definition: Callable=None, old: Key=None): + def bind_key(self, key: Key, definition: Callable=None, old: Key=None): """Binds a key to either a function or a key that previously had a binding Parameters From 6d260445c3e1a70c8dc6068306ec5d6e2a7616ff Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 14:03:49 -0500 Subject: [PATCH 16/20] Removed string formatters to comply with python3.5 --- py_cui/keys.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/py_cui/keys.py b/py_cui/keys.py index d0a1661..123360c 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -152,13 +152,13 @@ def bind_key(self, key: Key, definition: Callable=None, old: Key=None): The already bound key to bind to """ if not isinstance(key, Key): - raise ValueError(f"{key} is an invalid value for key") + raise ValueError("{} is an invalid value for key".format(key)) if old and old.value not in self._bindings: - raise ValueError(f"{old} is not in the bindings list, cannot bind to it") + raise ValueError("{old} is not in the bindings list, cannot bind to it".format(key)) if not old and not definition: - raise ValueError(f"Either old or definition must be defined") + raise ValueError("Either old or definition must be defined".format(key)) if old and definition: - raise ValueError(f"Cannot bind to both a key and a callable") + raise ValueError("Cannot bind to both a key and a callable".format(key)) if old: self._bindings[key.value] = self._bindings[old.value] else: @@ -173,7 +173,7 @@ def execute(self, key: Key): The key to execute """ if not isinstance(key, Key): - raise ValueError(f"{key} is an invalid value for key") + raise ValueError("{} is an invalid value for key".format(key)) elif key.value not in self._bindings.keys(): return @@ -192,7 +192,7 @@ def unbind(self, key: Key): def __add__(self, value): if not isinstance(value, self.__class__): - raise ValueError(f"Cannot add a KeyMap and {value.__class__} type") + raise ValueError("Cannot add a KeyMap and {} type".format(value.__class)) else: k = KeyMap() k._bindings = {**value._binings, **self._bindings} From 95b87880cd54f85cf0a78d0a33b4837a5cfab737 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 14:21:03 -0500 Subject: [PATCH 17/20] Updated examples to match new api --- examples/simple_todo_list.py | 8 ++++---- examples/snano.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/simple_todo_list.py b/examples/simple_todo_list.py index b8132dc..a7a8cee 100644 --- a/examples/simple_todo_list.py +++ b/examples/simple_todo_list.py @@ -30,9 +30,9 @@ def __init__(self, master): self.save_todo_button = self.master.add_button('Save', 7, 4, column_span=2, command=self.save_todo_file) # add some custom keybindings - self.new_todo_textbox.add_key_command( py_cui.keys.KEY_ENTER, self.push_and_reset) - self.todo_scroll_cell.add_key_command( py_cui.keys.KEY_ENTER, self.mark_as_in_progress) - self.in_progress_scroll_cell.add_key_command( py_cui.keys.KEY_ENTER, self.mark_as_done) + self.new_todo_textbox.add_key_command( py_cui.keys.Key.ENTER, self.push_and_reset) + self.todo_scroll_cell.add_key_command( py_cui.keys.Key.ENTER, self.mark_as_in_progress) + self.in_progress_scroll_cell.add_key_command( py_cui.keys.Key.ENTER, self.mark_as_done) self.read_todo_file() @@ -134,4 +134,4 @@ def save_todo_file(self): root = py_cui.PyCUI(8, 6) root.set_title('CUI TODO List') s = SimpleTodoList(root) -root.start() \ No newline at end of file +root.start() diff --git a/examples/snano.py b/examples/snano.py index 7d43d3b..0c324c9 100644 --- a/examples/snano.py +++ b/examples/snano.py @@ -26,15 +26,15 @@ def __init__(self, root, dir): self.dir = dir # If we press 's' we want to save the opened file - self.root.add_key_command(py_cui.keys.KEY_S_LOWER, self.save_opened_file) + self.root.add_key_command(py_cui.keys.Key.S_LOWER, self.save_opened_file) # Add a file selection menu self.file_menu = self.root.add_scroll_menu('Directory Files', 0, 0, row_span=5, column_span=2) # With ENTER key press, we open the selected file or directory, DELETE will delete the selected file or directory # See the callback functions for how these operations are performed - self.file_menu.add_key_command(py_cui.keys.KEY_ENTER, self.open_file_dir) - self.file_menu.add_key_command(py_cui.keys.KEY_DELETE, self.delete_selected_file) + self.file_menu.add_key_command(py_cui.keys.Key.ENTER, self.open_file_dir) + self.file_menu.add_key_command(py_cui.keys.Key.DELETE, self.delete_selected_file) # To better distingusish directories, add a color rule that is used to color directory names green # First parameter is a regex, second is color, third is how the rule should check the regex against the line. @@ -46,7 +46,7 @@ def __init__(self, root, dir): self.current_dir_box.set_text(self.dir) # You can manually enter directory path, and ENTER will try to open it - self.current_dir_box.add_key_command(py_cui.keys.KEY_ENTER, self.open_new_directory) + self.current_dir_box.add_key_command(py_cui.keys.Key.ENTER, self.open_new_directory) # Function that opens initial directory self.open_new_directory() @@ -56,7 +56,7 @@ def __init__(self, root, dir): # Add a textbox for adding new files on ENTER key self.new_file_textbox = self.root.add_text_box('Add New File', 5, 0, column_span=2) - self.new_file_textbox.add_key_command(py_cui.keys.KEY_ENTER, self.add_new_file) + self.new_file_textbox.add_key_command(py_cui.keys.Key.ENTER, self.add_new_file) def open_new_directory(self): From 4f53085bf42a4297967422ad817fb40a648e85ed Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 17:39:41 -0500 Subject: [PATCH 18/20] Updated multi-window example --- examples/multi_window_demo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/multi_window_demo.py b/examples/multi_window_demo.py index 609a62f..6c8b5de 100644 --- a/examples/multi_window_demo.py +++ b/examples/multi_window_demo.py @@ -25,7 +25,7 @@ def __init__(self, root): # Add a text box to the second widget set self.text_box_B = self.widget_set_B.add_text_box('Enter something', 0, 0, column_span=2) - self.text_box_B.add_key_command(py_cui.keys.KEY_ENTER, self.open_set_A) + self.text_box_B.add_key_command(py_cui.keys.Key.ENTER, self.open_set_A) def open_set_A(self): @@ -41,4 +41,4 @@ def open_set_B(self): # Create CUI object, pass to wrapper class, and start the CUI root = py_cui.PyCUI(3, 3) wrapper = MultiWindowDemo(root) -root.start() \ No newline at end of file +root.start() From 2d04bf8686511fb7b3e56478d57aea8f50d486c1 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 17:41:01 -0500 Subject: [PATCH 19/20] Re-added add_key_command and fixed issue with window closing --- py_cui/__init__.py | 28 +++++++++++++++++++++------- py_cui/widget_set.py | 7 +++---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index e548077..997b4b5 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -100,7 +100,7 @@ class PyCUI: a status bar object that gets drawn at the top of the CUI status_bar : py_cui.statusbar.StatusBar a status bar object that gets drawn at the bottom of the CUI - keybindings : list of py_cui.keybinding.KeyBinding + key_map : py_cui.keys.KeyMap list of keybindings to check against in the main CUI loop height, width : int height of the terminal in characters, width of terminal in characters @@ -151,6 +151,20 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True): # Callback to fire when CUI is stopped. self.on_stop = None + def add_key_command(self, key, command): + """Adds a new command to the given key + + *DEPRICATED* + + Parameters + ---------- + key : py_cui.keys.Key + The key to add a command to + command : Callable + The command to execute + """ + self.key_map.bind_key(key=key, definition=lambda x: command()) + def load_default_keymap(self): stop_wrapper = lambda x: self.stop() self.key_map.bind_key(key=py_cui.keys.Key.Q_LOWER, definition=stop_wrapper) @@ -173,7 +187,7 @@ def get_widget_set(self): new_widget_set = widget_set.WidgetSet(self.grid.num_rows, self.grid.num_columns) new_widget_set.grid = self.grid new_widget_set.widgets = self.widgets - new_widget_set.keybindings = self.keybindings + new_widget_set.key_map = self.key_map return new_widget_set @@ -190,7 +204,6 @@ def apply_widget_set(self, new_widget_set): self.lose_focus() self.widgets = new_widget_set.widgets self.grid = new_widget_set.grid - self.keybindings = new_widget_set.keybindings term_size = shutil.get_terminal_size() height = term_size.lines width = term_size.columns @@ -699,7 +712,7 @@ def get_widget_id(self, widget): return widget.id - + @py_cui.keys.ignore_key def lose_focus(self): """Function that forces py_cui out of focus mode. @@ -1039,7 +1052,6 @@ def handle_key_presses(self, key_pressed): # Selected widget represents which widget is being hovered over, though not necessarily in focus mode if self.selected_widget is None: return - # Otherwise, barring a popup, we are in overview mode, meaning that arrow py_cui.keys move between widgets, and Enter key starts focus mode if not self.popup and self.selected_widget and self.in_focused_mode: self.widgets[self.selected_widget].handle_key_press(key_pressed) @@ -1100,7 +1112,8 @@ def draw(self, stdscr): self.post_loading_callback = None # Handle keypresses - self.handle_key_presses(key_pressed) + if key_pressed != 0: + self.handle_key_presses(key_pressed) # Draw status/title bar, and all widgets. Selected widget will be bolded. self.draw_status_bars(stdscr, height, width) @@ -1111,13 +1124,14 @@ def draw(self, stdscr): # Refresh the screen stdscr.refresh() - # Wait for next input if self.loading or self.post_loading_callback is not None: # When loading, refresh screen every quarter second time.sleep(0.25) # Need to reset key_pressed, because otherwise the previously pressed key will be used. key_pressed = 0 + elif self.stopped: + break else: key_pressed = stdscr.getch() diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index eb525bf..bd186da 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -9,6 +9,7 @@ # TODO: Should create an initial widget set in PyCUI class that widgets are added to by default. import shutil +import py_cui.keys import py_cui.widgets as widgets import py_cui.grid as grid @@ -35,7 +36,7 @@ def __init__(self, num_rows, num_cols): """ self.widgets = {} - self.keybindings = {} + self.key_map = py_cui.keys.KeyMap() term_size = shutil.get_terminal_size() self.height = term_size.lines @@ -68,9 +69,7 @@ def add_key_command(self, key, command): command : Function A no-arg or lambda function to fire on keypress """ - - self.keybindings[key] = command - + self.key_map.bind_key(key=key, definition=lambda x: command()) def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0): """Function that adds a new scroll menu to the CUI grid From 2fc8f82f8252901f4f70a77a9d3693a8e9decb43 Mon Sep 17 00:00:00 2001 From: telday Date: Sat, 7 Mar 2020 17:49:29 -0500 Subject: [PATCH 20/20] Updated autogit example --- examples/autogit.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/autogit.py b/examples/autogit.py index ea75b89..e7ce6e8 100644 --- a/examples/autogit.py +++ b/examples/autogit.py @@ -35,13 +35,13 @@ def __init__(self, root, dir): self.root.set_title('Autogit v{} - {}'.format(__version__, os.path.basename(self.dir))) # Keybindings when in overview mode, and set info bar - self.root.add_key_command(py_cui.keys.KEY_R_LOWER, self.refresh_git_status) - self.root.add_key_command(py_cui.keys.KEY_L_LOWER, self.show_log) - self.root.add_key_command(py_cui.keys.KEY_A_LOWER, self.add_all) - self.root.add_key_command(py_cui.keys.KEY_E_LOWER, self.open_editor) - self.root.add_key_command(py_cui.keys.KEY_F_LOWER, self.fetch_branch) - self.root.add_key_command(py_cui.keys.KEY_P_LOWER, self.push_branch) - self.root.add_key_command(py_cui.keys.KEY_M_LOWER, self.show_menu) + self.root.add_key_command(py_cui.keys.Key.R_LOWER, self.refresh_git_status) + self.root.add_key_command(py_cui.keys.Key.L_LOWER, self.show_log) + self.root.add_key_command(py_cui.keys.Key.A_LOWER, self.add_all) + self.root.add_key_command(py_cui.keys.Key.E_LOWER, self.open_editor) + self.root.add_key_command(py_cui.keys.Key.F_LOWER, self.fetch_branch) + self.root.add_key_command(py_cui.keys.Key.P_LOWER, self.push_branch) + self.root.add_key_command(py_cui.keys.Key.M_LOWER, self.show_menu) self.root.set_status_bar_text('Quit - q | Refresh - r | Add all - a | Git log - l | Open Editor - e | Pull Branch - f | Push Branch - p') # Create the add files menu. Add color rules to color first characters based on git status @@ -77,24 +77,24 @@ def __init__(self, root, dir): self.commit_message_box = self.root.add_text_box('Commit Message', 8, 2, column_span=6) # Key commands for our file menus. Enter will git add, Space will show diff - self.add_files_menu.add_key_command(py_cui.keys.KEY_ENTER, self.add_revert_file) - self.add_files_menu.add_key_command(py_cui.keys.KEY_SPACE, self.open_git_diff) + self.add_files_menu.add_key_command(py_cui.keys.Key.ENTER, self.add_revert_file) + self.add_files_menu.add_key_command(py_cui.keys.Key.SPACE, self.open_git_diff) self.add_files_menu.help_text = 'Enter - git add, Space - see diff, Arrows - scroll, Esc - exit' # Enter will show remote info - self.git_remotes_menu.add_key_command(py_cui.keys.KEY_ENTER, self.show_remote_info) + self.git_remotes_menu.add_key_command(py_cui.keys.Key.ENTER, self.show_remote_info) # Enter will show commit diff - self.git_commits_menu.add_key_command(py_cui.keys.KEY_ENTER, self.show_git_commit_diff) + self.git_commits_menu.add_key_command(py_cui.keys.Key.ENTER, self.show_git_commit_diff) self.git_commits_menu.add_text_color_rule(' ', py_cui.GREEN_ON_BLACK, 'notstartswith', match_type='region', region=[0,7], include_whitespace=True) # Enter will checkout - self.branch_menu.add_key_command(py_cui.keys.KEY_ENTER, self.checkout_branch) - self.branch_menu.add_key_command(py_cui.keys.KEY_SPACE, self.show_log) + self.branch_menu.add_key_command(py_cui.keys.Key.ENTER, self.checkout_branch) + self.branch_menu.add_key_command(py_cui.keys.Key.SPACE, self.show_log) # Add commands for committing and branch checkout. - self.new_branch_textbox.add_key_command(py_cui.keys.KEY_ENTER, self.create_new_branch) - self.commit_message_box.add_key_command(py_cui.keys.KEY_ENTER, self.ask_to_commit) + self.new_branch_textbox.add_key_command(py_cui.keys.Key.ENTER, self.create_new_branch) + self.commit_message_box.add_key_command(py_cui.keys.Key.ENTER, self.ask_to_commit) def get_logo(self):