From 237524c0f12ea7617de06f4c917a7d5dfa6418e4 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sun, 24 Oct 2021 11:18:40 +0200 Subject: [PATCH 01/30] Replace KEY_BACKSPACE by a list --- py_cui/dialogs/filedialog.py | 22 +++++++++--------- py_cui/dialogs/form.py | 44 ++++++++++++++++++------------------ py_cui/keys.py | 20 +++++----------- py_cui/popups.py | 21 ++++++++--------- py_cui/widgets.py | 14 ++++++------ 5 files changed, 56 insertions(+), 65 deletions(-) diff --git a/py_cui/dialogs/filedialog.py b/py_cui/dialogs/filedialog.py index 0b5b46c..55f6ac3 100644 --- a/py_cui/dialogs/filedialog.py +++ b/py_cui/dialogs/filedialog.py @@ -65,7 +65,7 @@ def __init__(self, elem_type: str, name: str, fullpath: str, ascii_icons: bool=F # for compatibility reasons. if not ascii_icons: self._folder_icon = '\U0001f4c1' - # Folder icon is two characters, so + # Folder icon is two characters, so self._file_icon = '\U0001f5ce' + ' ' else: self._folder_icon = '' @@ -121,7 +121,7 @@ def __init__(self, initial_loc: str, dialog_type: str, ascii_icons, logger, limi """ super().__init__(logger) - + self._current_dir = os.path.abspath(initial_loc) self._ascii_icons = ascii_icons self._dialog_type = dialog_type @@ -391,7 +391,7 @@ def _handle_key_press(self, key_pressed: int) -> None: self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed == py_cui.keys.KEY_BACKSPACE: + elif key_pressed in py_cui.keys.KEYS_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() @@ -416,7 +416,7 @@ def _handle_key_press(self, key_pressed: int) -> None: fp = open(new_elem, 'w') fp.close() self._parent_dialog._file_dir_select.refresh_view() - + except FileExistsError: self._parent_dialog.display_warning('File/Directory already exists!') except PermissionError: @@ -510,7 +510,7 @@ def get_absolute_stop_pos(self) -> Tuple[int,int]: def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: - """Handles mouse presses + """Handles mouse presses Parameters ---------- @@ -540,7 +540,7 @@ def _handle_key_press(self, key_pressed: int) -> None: if key_pressed == py_cui.keys.KEY_ENTER: self.perform_command() - + def perform_command(self) -> None: if self.command is not None: if self._button_num == 1: @@ -698,7 +698,7 @@ def output_valid(self, output) -> Tuple[bool,Optional[str]]: def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions - + Returns ------- start_x, start_y : int @@ -714,7 +714,7 @@ def get_absolute_start_pos(self) -> Tuple[int,int]: def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions - + Returns ------- stop_x, stop_y : int @@ -803,12 +803,12 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: self._filename_input.set_selected(False) self._file_dir_select.set_selected(True) self._file_dir_select._handle_mouse_press(x, y, mouse_event) - + elif self._filename_input._contains_position(x, y): self._filename_input.set_selected(True) self._file_dir_select.set_selected(False) self._filename_input._handle_mouse_press(x, y, mouse_event) - + elif self._submit_button._contains_position(x, y): self._submit_button._handle_mouse_press(x, y, mouse_event) @@ -818,7 +818,7 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: def _draw(self) -> None: """Override of base class. - + Here, we only draw a border, and then the individual form elements """ diff --git a/py_cui/dialogs/form.py b/py_cui/dialogs/form.py index af641ad..8cf7349 100644 --- a/py_cui/dialogs/form.py +++ b/py_cui/dialogs/form.py @@ -69,7 +69,7 @@ def is_valid(self) -> Tuple[bool,Optional[str]]: def is_required(self) -> bool: """Checks if field is required - + Returns ------- required : bool @@ -164,7 +164,7 @@ def _handle_key_press(self, key_pressed: int) -> None: self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed == py_cui.keys.KEY_BACKSPACE: + elif key_pressed in py_cui.keys.KEYS_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() @@ -225,7 +225,7 @@ def __init__(self, field_implementations: List['FormField'], required_fields: Li super().__init__(logger) self._form_fields = field_implementations self._required_fields = required_fields - + self._selected_form_index = 0 self._on_submit_action: Optional[Callable[[],Any]] = None @@ -366,17 +366,17 @@ def __init__(self, root, fields, passwd_fields, required_fields, fields_init_tex init_text = '' if field in fields_init_text: init_text = fields_init_text[field] - self._form_fields.append(FormFieldElement(self, - i, - field, - init_text, - (field in passwd_fields), - (field in required_fields), + self._form_fields.append(FormFieldElement(self, + i, + field, + init_text, + (field in passwd_fields), + (field in required_fields), renderer, logger)) self._form_fields[0].set_selected(True) FormImplementation.__init__(self, self._form_fields, required_fields, logger) - + self._internal_popup = None @@ -394,7 +394,7 @@ def get_num_fields(self) -> int: def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions - + Returns ------- start_x, start_y : int @@ -410,9 +410,9 @@ def get_absolute_start_pos(self) -> Tuple[int,int]: min_required_y = 4 + (2 * self._pady) + 5 * self._num_fields if root_height < min_required_y: min_required_y = root_height - + form_start_x = int(root_width / 2) - int(min_required_x / 2) - + form_start_y = int(root_height / 2) - int(min_required_y / 2) return form_start_x, form_start_y @@ -420,7 +420,7 @@ def get_absolute_start_pos(self) -> Tuple[int,int]: def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions - + Returns ------- stop_x, stop_y : int @@ -436,9 +436,9 @@ def get_absolute_stop_pos(self) -> Tuple[int,int]: min_required_y = 4 + (2 * self._pady) + 5 * self._num_fields if root_height < min_required_y: min_required_y = root_height - + form_stop_x = int(root_width / 2) + int(min_required_x / 2) - + form_stop_y = int(root_height / 2) + int(min_required_y / 2) return form_stop_x, form_stop_y @@ -479,14 +479,14 @@ def _handle_key_press(self, key_pressed: int) -> None: if valid: self._root.close_popup() if self._on_submit_action is not None: - self._on_submit_action(self.get()) + self._on_submit_action(self.get()) else: self._internal_popup = InternalFormPopup(self, - self._root, - err_msg, + self._root, + err_msg, f'Required fields: {str(self._required_fields)}', - py_cui.YELLOW_ON_BLACK, - self._renderer, + py_cui.YELLOW_ON_BLACK, + self._renderer, self._logger) elif key_pressed == py_cui.keys.KEY_ESCAPE: self._root.close_popup() @@ -519,7 +519,7 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: def _draw(self) -> None: """Override of base class. - + Here, we only draw a border, and then the individual form elements """ diff --git a/py_cui/keys.py b/py_cui/keys.py index af6fb00..6a4fbd8 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -18,7 +18,7 @@ def get_ascii_from_char(char: str) -> int: ---------- char : character character to convert to ascii - + Returns ------- ascii_code : int @@ -56,17 +56,9 @@ def get_char_from_ascii(key_num: int) -> Optional[str]: KEY_DELETE = curses.KEY_DC KEY_TAB = get_ascii_from_char('\t') -# Pressing backspace returns 8 on windows? -if platform == 'win32': - KEY_BACKSPACE = 8 -# Adds support for 'delete/backspace' key on OSX -elif platform == 'darwin': - KEY_BACKSPACE = 127 -else: - KEY_BACKSPACE = curses.KEY_BACKSPACE - -SPECIAL_KEYS = [KEY_ENTER, KEY_ESCAPE, KEY_SPACE, KEY_DELETE, KEY_TAB, KEY_BACKSPACE] +KEYS_BACKSPACE = [8, 127, curses.KEY_BACKSPACE] +SPECIAL_KEYS = [KEY_ENTER, KEY_ESCAPE, KEY_SPACE, KEY_DELETE, KEY_TAB] + KEYS_BACKSPACE # Page navigation keys KEY_PAGE_UP = curses.KEY_PPAGE @@ -85,13 +77,13 @@ def get_char_from_ascii(key_num: int) -> Optional[str]: if platform == 'linux' or platform == 'darwin': - + # Modified arrow keys KEY_SHIFT_LEFT = 393 KEY_SHIFT_RIGHT = 402 KEY_SHIFT_UP = 337 KEY_SHIFT_DOWN = 336 - + KEY_CTRL_LEFT = 560 KEY_CTRL_RIGHT = 545 KEY_CTRL_UP = 566 @@ -103,7 +95,7 @@ def get_char_from_ascii(key_num: int) -> Optional[str]: KEY_SHIFT_RIGHT = 400 KEY_SHIFT_UP = 547 KEY_SHIFT_DOWN = 548 - + KEY_CTRL_LEFT = 443 KEY_CTRL_RIGHT = 444 KEY_CTRL_UP = 480 diff --git a/py_cui/popups.py b/py_cui/popups.py index 470225f..9df78f1 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -49,7 +49,7 @@ def __init__(self, root: 'py_cui.PyCUI', title: str, text: str, color: int, rend self._selected_color = color self.update_height_width() - + def _increment_counter(self): """Function that increments an internal counter """ @@ -71,7 +71,7 @@ def set_text(self, text: str) -> None : def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions - + Returns ------- start_x, start_y : int @@ -84,7 +84,7 @@ def get_absolute_start_pos(self) -> Tuple[int,int]: def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions - + Returns ------- stop_x, stop_y : int @@ -138,11 +138,10 @@ def __init__(self, root, title, text, color, renderer, logger): """ super().__init__(root, title, text, color, renderer, logger) - 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_DELETE] + py_cui.keys.KEYS_BACKSPACE def _draw(self) -> None: @@ -284,7 +283,7 @@ def _handle_key_press(self, key_pressed: int): self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed == py_cui.keys.KEY_BACKSPACE: + elif key_pressed in py_cui.keys.KEYS_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() @@ -481,7 +480,7 @@ def _draw(self) -> None: self._icon_counter = self._icon_counter + 1 if self._icon_counter == len(self._loading_icons): self._icon_counter = 0 - + # Use Superclass draw after new text is computed super()._draw() @@ -554,6 +553,6 @@ def _draw(self) -> None: self.set_text(f'{"#" * completed_blocks}{"-" * non_completed_blocks} \ ({self._completed_items}/{self._num_items}) {self._loading_icons[self._icon_counter]}') - + # Use Superclass draw after new text is computed super()._draw() diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 56b38d4..eb0a379 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -306,7 +306,7 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int): # Retrieve the command function if it exists if mouse_event in self._mouse_commands.keys(): command = self._mouse_commands[mouse_event] - + # Identify num of args from callable. This allows for user to create commands that take in x, y # coords of the mouse press as input num_args = 0 @@ -486,7 +486,7 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int): if viewport_top <= y and viewport_top + len(self._view_items) - self._top_view >= y: elem_clicked = y - viewport_top + self._top_view self.set_selected_item_index(elem_clicked) - + if self.get_selected_item_index() != current and self._on_selection_change is not None: self._process_selection_change_event() @@ -511,7 +511,7 @@ def _handle_key_press(self, key_pressed: int) -> None: current = self.get_selected_item_index() viewport_height = self.get_viewport_height() - + if key_pressed == py_cui.keys.KEY_UP_ARROW: self._scroll_up() if key_pressed == py_cui.keys.KEY_DOWN_ARROW: @@ -671,7 +671,7 @@ def __init__(self, id, title: str, grid: 'py_cui.grid.Grid', row: int, column: i self.command = command self.set_color(py_cui.MAGENTA_ON_BLACK) self.set_help_text('Focus mode on Button. Press Enter to press button, Esc to exit focus mode.') - + # By default we will process command on click or double click if self.command is not None: self.add_mouse_command(py_cui.keys.LEFT_MOUSE_CLICK, self.command) @@ -773,7 +773,7 @@ def _handle_key_press(self, key_pressed: int) -> None: self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed == py_cui.keys.KEY_BACKSPACE: + elif key_pressed in py_cui.keys.KEYS_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() @@ -857,7 +857,7 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """ Widget._handle_mouse_press(self, x, y, mouse_event) - + if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK: if y >= self._cursor_max_up and y <= self._cursor_max_down: if x >= self._cursor_max_left and x <= self._cursor_max_right: @@ -901,7 +901,7 @@ def _handle_key_press(self, key_pressed: int) -> None: # TODO: Fix this janky operation here 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: + elif key_pressed in py_cui.keys.KEYS_BACKSPACE: self._handle_backspace() elif key_pressed == py_cui.keys.KEY_DELETE: self._handle_delete() From 73b65db1ccc7dd44c70f418f31655b8c599ecad6 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Fri, 29 Oct 2021 22:49:58 +0200 Subject: [PATCH 02/30] Handle keys lists in add_key_command --- py_cui/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 66c585d..ecfc024 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -22,7 +22,7 @@ # there is a open source windows-curses module that adds curses support # for python on windows import curses -from typing import Any, Callable, List, Dict, Optional, Tuple +from typing import Any, Union, Callable, List, Dict, Optional, Tuple # py_cui imports import py_cui @@ -1145,18 +1145,22 @@ def _cycle_widgets(self, reverse: bool=False) -> None: self.move_focus(next_widget, auto_press_buttons=False) - def add_key_command(self, key: int, command: Callable[[],Any]) -> None: + def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) -> None: """Function that adds a keybinding to the CUI when in overview mode Parameters ---------- - key : py_cui.keys.KEY_* + key : py_cui.keys.* The key bound to the command command : Function A no-arg or lambda function to fire on keypress """ - self._keybindings[key] = command + if isinstance(key, list): + for value in key: + self._keybindings[value] = command + else: + self._keybindings[key] = command # Popup functions. Used to display messages, warnings, and errors to the user. From 21539b7a7847afca1d33139fced7878ac87619a0 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Fri, 29 Oct 2021 17:16:41 -0400 Subject: [PATCH 03/30] Bump version to 0.1.5 --- py_cui/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 66c585d..fb34835 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -39,7 +39,7 @@ from py_cui.colors import * # Version number -__version__ = '0.1.4' +__version__ = '0.1.5' def fit_text(width: int, text: str, center: bool = False) -> str: diff --git a/setup.py b/setup.py index d22e53f..d93f979 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ description='A widget and grid based framework for building command line user interfaces in python.', long_description=long_description, long_description_content_type='text/markdown', - version='0.1.4', + version='0.1.5, author='Jakub Wlodek', author_email='jwlodek.dev@gmail.com', license='BSD (3-clause)', From f868d0c2e7a7c291ed02ef743cf32b9ace756596 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 30 Oct 2021 12:15:46 +0200 Subject: [PATCH 04/30] Revert `KEY_BACKSPACE` var name --- py_cui/dialogs/filedialog.py | 2 +- py_cui/dialogs/form.py | 2 +- py_cui/keys.py | 4 ++-- py_cui/popups.py | 4 ++-- py_cui/widgets.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/py_cui/dialogs/filedialog.py b/py_cui/dialogs/filedialog.py index 55f6ac3..0251e25 100644 --- a/py_cui/dialogs/filedialog.py +++ b/py_cui/dialogs/filedialog.py @@ -391,7 +391,7 @@ def _handle_key_press(self, key_pressed: int) -> None: self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed in py_cui.keys.KEYS_BACKSPACE: + elif key_pressed in py_cui.keys.KEY_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() diff --git a/py_cui/dialogs/form.py b/py_cui/dialogs/form.py index 8cf7349..ceac820 100644 --- a/py_cui/dialogs/form.py +++ b/py_cui/dialogs/form.py @@ -164,7 +164,7 @@ def _handle_key_press(self, key_pressed: int) -> None: self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed in py_cui.keys.KEYS_BACKSPACE: + elif key_pressed in py_cui.keys.KEY_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() diff --git a/py_cui/keys.py b/py_cui/keys.py index 6a4fbd8..76bde6a 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -56,9 +56,9 @@ def get_char_from_ascii(key_num: int) -> Optional[str]: KEY_DELETE = curses.KEY_DC KEY_TAB = get_ascii_from_char('\t') -KEYS_BACKSPACE = [8, 127, curses.KEY_BACKSPACE] +KEY_BACKSPACE = [8, 127, curses.KEY_BACKSPACE] -SPECIAL_KEYS = [KEY_ENTER, KEY_ESCAPE, KEY_SPACE, KEY_DELETE, KEY_TAB] + KEYS_BACKSPACE +SPECIAL_KEYS = [KEY_ENTER, KEY_ESCAPE, KEY_SPACE, KEY_DELETE, KEY_TAB] + KEY_BACKSPACE # Page navigation keys KEY_PAGE_UP = curses.KEY_PPAGE diff --git a/py_cui/popups.py b/py_cui/popups.py index 9df78f1..3d21258 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -141,7 +141,7 @@ def __init__(self, root, title, text, color, renderer, logger): self._close_keys = [ py_cui.keys.KEY_ENTER, py_cui.keys.KEY_ESCAPE, py_cui.keys.KEY_SPACE, - py_cui.keys.KEY_DELETE] + py_cui.keys.KEYS_BACKSPACE + py_cui.keys.KEY_DELETE] + py_cui.keys.KEY_BACKSPACE def _draw(self) -> None: @@ -283,7 +283,7 @@ def _handle_key_press(self, key_pressed: int): self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed in py_cui.keys.KEYS_BACKSPACE: + elif key_pressed in py_cui.keys.KEY_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() diff --git a/py_cui/widgets.py b/py_cui/widgets.py index eb0a379..b93d541 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -773,7 +773,7 @@ def _handle_key_press(self, key_pressed: int) -> None: self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: self._move_right() - elif key_pressed in py_cui.keys.KEYS_BACKSPACE: + elif key_pressed in py_cui.keys.KEY_BACKSPACE: self._erase_char() elif key_pressed == py_cui.keys.KEY_DELETE: self._delete_char() @@ -901,7 +901,7 @@ def _handle_key_press(self, key_pressed: int) -> None: # TODO: Fix this janky operation here 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 in py_cui.keys.KEYS_BACKSPACE: + elif key_pressed in py_cui.keys.KEY_BACKSPACE: self._handle_backspace() elif key_pressed == py_cui.keys.KEY_DELETE: self._handle_delete() From 77da0f2921b6d8dc972ea1f96653f0ef0f9b469f Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 30 Oct 2021 12:22:09 +0200 Subject: [PATCH 05/30] Add key list handling in widgets --- py_cui/widget_set.py | 16 ++++++++++------ py_cui/widgets.py | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 94e6791..eec46a5 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -9,7 +9,7 @@ # TODO: Should create an initial widget set in PyCUI class that widgets are added to by default. import shutil -from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING +from typing import Any, Union, Callable, Dict, List, Optional, TYPE_CHECKING import py_cui.widgets as widgets import py_cui.grid as grid import py_cui.controls as controls @@ -91,7 +91,7 @@ def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']]: return self._widgets - def add_key_command(self, key: int, command: Callable[[],Any]): + def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) -> None: """Function that adds a keybinding to the CUI when in overview mode Parameters @@ -102,7 +102,11 @@ def add_key_command(self, key: int, command: Callable[[],Any]): A no-arg or lambda function to fire on keypress """ - self._keybindings[key] = command + if isinstance(key, list): + for value in key: + self._keybindings[value] = command + else: + self._keybindings[key] = command def add_scroll_menu(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0) -> 'py_cui.widgets.ScrollMenu': @@ -427,9 +431,9 @@ def add_button(self, title: str, row: int, column: int, row_span: int = 1, colum def add_slider(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0, - min_val: int=0, max_val: int=100, step: int=1, init_val: int=0) -> 'py_cui.controls.slider.SliderWidget': - - + min_val: int=0, max_val: int=100, step: int=1, init_val: int=0) -> 'py_cui.controls.slider.SliderWidget': + + """Function that adds a new label to the CUI grid Parameters diff --git a/py_cui/widgets.py b/py_cui/widgets.py index b93d541..a0fc215 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -28,7 +28,7 @@ import py_cui.colors import py_cui.errors -from typing import Callable, List, Dict, Tuple, Any, Optional +from typing import Union, Callable, List, Dict, Tuple, Any, Optional class Widget(py_cui.ui.UIElement): @@ -84,18 +84,22 @@ def __init__(self, id, title: str, grid: 'py_cui.grid.Grid', row: int, column: i self.update_height_width() - def add_key_command(self, key: int, command: Callable[[],Any]) -> Any: - """Maps a keycode to a function that will be executed when in focus mode + def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) -> None: + """Function that adds a keybinding to the CUI when in overview mode Parameters - ---------- - key : py_cui.keys.KEY + ----------c + key : py_cui.keys.* 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 + if isinstance(key, list): + for value in key: + self._keybindings[value] = command + else: + self._keybindings[key] = command def add_mouse_command(self, mouse_event: int, command: Callable[[],Any]) -> None: @@ -123,12 +127,12 @@ def add_mouse_command(self, mouse_event: int, command: Callable[[],Any]) -> None self._mouse_commands[mouse_event] = command - def update_key_command(self, key: int, command: Callable[[],Any]) -> Any: + def update_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) -> Any: """Maps a keycode to a function that will be executed when in focus mode, if key is already mapped Parameters ---------- - key : py_cui.keys.KEY + 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 From 301db69c8ea0ec92a27b4705efb7ef16c7372431 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 30 Oct 2021 12:28:42 +0200 Subject: [PATCH 06/30] `add_key_command` docstrings fix --- py_cui/__init__.py | 76 ++++++++++++++++++++++---------------------- py_cui/widget_set.py | 2 +- py_cui/widgets.py | 6 ++-- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index ecfc024..1243b2a 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -113,7 +113,7 @@ def __init__(self, num_rows: int, num_cols: int, auto_focus_buttons: bool=True, """ self._title = 'PyCUI Window' - + # When this is not set, the escape character delay # is too long for exiting focus mode os.environ.setdefault('ESCDELAY', '25') @@ -211,7 +211,7 @@ def set_on_draw_update_func(self, update_function: Callable[[],Any]): self._on_draw_update_func = update_function - def set_widget_cycle_key(self, forward_cycle_key: int=None, reverse_cycle_key: int=None) -> None: + def set_widget_cycle_key(self, forward_cycle_key: int=None, reverse_cycle_key: int=None) -> None: """Assigns a key for automatically cycling through widgets in both focus and overview modes Parameters @@ -437,12 +437,12 @@ def set_widget_border_characters(self, upper_left_corner: str, upper_right_corne self._logger.debug(f'Set border_characters to {self._border_characters}') - def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']]: + def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']]: """Function that gets current set of widgets Returns ------- - widgets : dict of int -> widget + widgets : dict of int -> widget dictionary mapping widget IDs to object instances """ @@ -784,7 +784,7 @@ def add_button(self, title: str, row: int, column: int, row_span: int = 1, colum self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') return new_button - + def add_slider(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0, min_val: int=0, max_val: int=100, step: int=1, init_val: int=0) -> 'py_cui.controls.slider.SliderWidget': @@ -850,7 +850,7 @@ def forget_widget(self, widget : 'py_cui.widgets.Widget') -> None: ---------- widget : py_cui.widgets.Widget Widget to remove from the UI - + Raises ------ TypeError @@ -865,7 +865,7 @@ def forget_widget(self, widget : 'py_cui.widgets.Widget') -> None: raise KeyError(f'Widget with id {widget.get_id()} has already been removed from the UI!') else: self.get_widgets()[widget.get_id()] = None - + def get_element_at_position(self, x: int, y: int) -> Optional['py_cui.ui.UIElement']: """Returns containing widget for character position @@ -888,10 +888,10 @@ def get_element_at_position(self, x: int, y: int) -> Optional['py_cui.ui.UIEleme elif self._popup is None: for widget_id in self.get_widgets().keys(): - widget = self.get_widgets()[widget_id] + widget = self.get_widgets()[widget_id] if widget is not None: if widget._contains_position(x, y): - return widget + return widget return None @@ -977,7 +977,7 @@ def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', direction: in for row in range(row_range_start, row_range_stop): for col in range(col_start, col_start + col_span): for widget_id in self.get_widgets().keys(): - item_value = self.get_widgets()[widget_id] + item_value = self.get_widgets()[widget_id] if item_value is not None: if item_value._is_row_col_inside(row, col) and widget_id not in id_list: id_list.append(widget_id) @@ -1005,7 +1005,7 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: Returns ------- - widget_id : int + widget_id : int The widget neighbor ID if found, None otherwise """ @@ -1048,7 +1048,7 @@ def set_selected_widget(self, widget_id: int) -> None: Parameters ---------- - widget_id : int + widget_id : int the id of the widget to select """ @@ -1135,8 +1135,8 @@ def _cycle_widgets(self, reverse: bool=False) -> None: current_widget_id: int = current_widget_num next_widget_id: int = next_widget_num - current_widget = self.get_widgets()[current_widget_id] - next_widget = self.get_widgets()[next_widget_id] + current_widget = self.get_widgets()[current_widget_id] + next_widget = self.get_widgets()[next_widget_id] if current_widget and next_widget is not None: #pls check again if self._in_focused_mode and cycle_key in current_widget._key_commands.keys(): # In the event that we are focusing on a widget with that key defined, we do not cycle. @@ -1150,8 +1150,8 @@ def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) Parameters ---------- - key : py_cui.keys.* - The key bound to the command + key : py_cui.keys.KEY_* + ascii keycode used to map the key command : Function A no-arg or lambda function to fire on keypress """ @@ -1336,14 +1336,14 @@ def show_form_popup(self, title: str, fields: List[str], passwd_fields: List[str If not none, fired after loading is completed. Must be a no-arg function """ - self._popup = py_cui.dialogs.form.FormPopup(self, - fields, - passwd_fields, - required, - {}, - title, - py_cui.WHITE_ON_BLACK, - self._renderer, + self._popup = py_cui.dialogs.form.FormPopup(self, + fields, + passwd_fields, + required, + {}, + title, + py_cui.WHITE_ON_BLACK, + self._renderer, self._logger) if callback is not None: @@ -1372,14 +1372,14 @@ def show_filedialog_popup(self, popup_type: str='openfile', initial_dir: str ='. Only show files with extensions in this list if not empty. Default, [] """ - self._popup = py_cui.dialogs.filedialog.FileDialogPopup(self, - callback, - initial_dir, - popup_type, - ascii_icons, - limit_extensions, - py_cui.WHITE_ON_BLACK, - self._renderer, + self._popup = py_cui.dialogs.filedialog.FileDialogPopup(self, + callback, + initial_dir, + popup_type, + ascii_icons, + limit_extensions, + py_cui.WHITE_ON_BLACK, + self._renderer, self._logger) self._logger.debug(f'Opened {str(type(self._popup))} popup with type {popup_type}') @@ -1416,7 +1416,7 @@ def close_popup(self) -> None: def _refresh_height_width(self) -> None: """Function that updates the height and width of the CUI based on terminal window size.""" - + if self._simulated_terminal is None: if self._stdscr is None: term_size = shutil.get_terminal_size() @@ -1430,7 +1430,7 @@ def _refresh_height_width(self) -> None: width = self._simulated_terminal[1] height = height - self.title_bar.get_height() - self.status_bar.get_height() - 2 - + self._logger.debug(f'Resizing CUI to new dimensions {height} by {width}') self._height = height @@ -1464,7 +1464,7 @@ def _draw_widgets(self) -> None: for widget_id in self.get_widgets().keys(): if widget_id != self._selected_widget: - widget = self.get_widgets()[widget_id] + widget = self.get_widgets()[widget_id] if widget is not None: widget._draw() @@ -1591,7 +1591,7 @@ def _handle_key_presses(self, key_pressed: int) -> None: self._popup._handle_key_press(key_pressed) - def _draw(self, stdscr) -> None: + def _draw(self, stdscr) -> None: """Main CUI draw loop called by start() Parameters @@ -1613,7 +1613,7 @@ def _draw(self, stdscr) -> None: # Initialization functions. Generates colors and renderer self._initialize_colors() self._initialize_widget_renderer() - + # If user specified a refresh timeout, apply it here if self._refresh_timeout > 0: self._stdscr.timeout(self._refresh_timeout) @@ -1651,7 +1651,7 @@ def _draw(self, stdscr) -> None: # Here we handle mouse click events globally, or pass them to the UI element to handle elif key_pressed == curses.KEY_MOUSE: self._logger.info('Detected mouse click') - + valid_mouse_event = True try: id, x, y, _, mouse_event = curses.getmouse() diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index eec46a5..0e4357a 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -97,7 +97,7 @@ def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) Parameters ---------- key : py_cui.keys.KEY_* - The key bound to the command + ascii keycode used to map the key 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 a0fc215..09e1754 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -85,11 +85,11 @@ def __init__(self, id, title: str, grid: 'py_cui.grid.Grid', row: int, column: i def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) -> None: - """Function that adds a keybinding to the CUI when in overview mode + """Maps a keycode to a function that will be executed when in focus mode Parameters - ----------c - key : py_cui.keys.* + ---------- + 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 From d360b64fd1a15802f7ecf818515bcc8a8a574d95 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 30 Oct 2021 17:03:27 +0200 Subject: [PATCH 07/30] Format `__init__` with autopep8 --- .pep8 | 5 + py_cui/__init__.py | 524 +++++++++++++++++++++++++++++---------------- 2 files changed, 340 insertions(+), 189 deletions(-) create mode 100644 .pep8 diff --git a/.pep8 b/.pep8 new file mode 100644 index 0000000..2fc42cc --- /dev/null +++ b/.pep8 @@ -0,0 +1,5 @@ +[pycodestyle] +max_line_length = 88 +in-place = true +recursive = true +aggressive = 3 diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 0875c9a..e92d17e 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -107,8 +107,13 @@ class PyCUI: Dimensions for an alternative simulated terminal (used for testing) """ - def __init__(self, num_rows: int, num_cols: int, auto_focus_buttons: bool=True, - exit_key=py_cui.keys.KEY_Q_LOWER, simulated_terminal: List[int]=None): + def __init__( + self, + num_rows: int, + num_cols: int, + auto_focus_buttons: bool = True, + exit_key=py_cui.keys.KEY_Q_LOWER, + simulated_terminal: List[int] = None): """Initializer for PyCUI class """ @@ -124,67 +129,72 @@ def __init__(self, num_rows: int, num_cols: int, auto_focus_buttons: bool=True, if self._simulated_terminal is None: term_size = shutil.get_terminal_size() - height = term_size.lines - width = term_size.columns + height = term_size.lines + width = term_size.columns else: height = self._simulated_terminal[0] - width = self._simulated_terminal[1] + width = self._simulated_terminal[1] # Add status and title bar - self.title_bar = py_cui.statusbar.StatusBar(self._title, BLACK_ON_WHITE, root=self, is_title_bar=True) + self.title_bar = py_cui.statusbar.StatusBar( + self._title, BLACK_ON_WHITE, root=self, is_title_bar=True) exit_key_char = py_cui.keys.get_char_from_ascii(exit_key) if exit_key_char: - self._init_status_bar_text = f'Press - {exit_key_char} - to exit. Arrow ' \ - 'Keys to move between widgets. Enter to ' \ - 'enter focus mode.' + self._init_status_bar_text = f'Press - {exit_key_char} - to exit. Arrow ' \ + 'Keys to move between widgets. Enter to ' \ + 'enter focus mode.' else: - self._init_status_bar_text = 'Press arrow Keys to move between widgets. ' \ - 'Enter to enter focus mode.' \ + self._init_status_bar_text = 'Press arrow Keys to move between widgets. ' \ + 'Enter to enter focus mode.' \ self.status_bar = py_cui.statusbar.StatusBar(self._init_status_bar_text, BLACK_ON_WHITE, root=self) # Init terminal height width. Subtract 4 from height # for title/status bar and padding - self._height = height - self._width = width - self._height = self._height - self.title_bar.get_height() - self.status_bar.get_height() - 2 + self._height = height + self._width = width + self._height = self._height - self.title_bar.get_height() - self.status_bar.get_height() - 2 # Logging object initialization for py_cui self._logger = py_cui.debug._initialize_logger(self, name='py_cui') # Initialize grid, renderer, and widget dict - self._grid = py_cui.grid.Grid(num_rows, num_cols, self._height, self._width, self._logger) - self._stdscr: Any = None - self._refresh_timeout = -1 - self._border_characters: Optional[Dict[str,str]] = None - self._widgets: Dict[int,Optional['py_cui.widgets.Widget']] = {} + self._grid = py_cui.grid.Grid( + num_rows, + num_cols, + self._height, + self._width, + self._logger) + self._stdscr: Any = None + self._refresh_timeout = -1 + self._border_characters: Optional[Dict[str, str]] = None + self._widgets: Dict[int, Optional['py_cui.widgets.Widget']] = {} self._renderer: Optional['py_cui.renderer.Renderer'] = None # Variables for determining selected widget/focus mode self._selected_widget: Optional[int] = None - self._in_focused_mode = False + self._in_focused_mode = False self._popup: Any = None - self._auto_focus_buttons = auto_focus_buttons + self._auto_focus_buttons = auto_focus_buttons # CUI blocks when loading popup is open - self._loading = False - self._stopped = False - self._post_loading_callback: Optional[Callable[[],Any]] = None - self._on_draw_update_func: Optional[Callable[[],Any]] = None + self._loading = False + self._stopped = False + self._post_loading_callback: Optional[Callable[[], Any]] = None + self._on_draw_update_func: Optional[Callable[[], Any]] = None # Top level keybindings. Exit key is 'q' by default - self._keybindings: Dict[int,Callable[[],Any]] = {} - self._exit_key = exit_key + self._keybindings: Dict[int, Callable[[], Any]] = {} + self._exit_key = exit_key self._forward_cycle_key = py_cui.keys.KEY_CTRL_LEFT self._reverse_cycle_key = py_cui.keys.KEY_CTRL_RIGHT self._toggle_live_debug_key: Optional[int] = None # Callback to fire when CUI is stopped. - self._on_stop: Optional[Callable[[],Any]] = None - + self._on_stop: Optional[Callable[[], Any]] = None def set_refresh_timeout(self, timeout: int): """Sets the CUI auto-refresh timeout to a number of seconds. @@ -198,8 +208,7 @@ def set_refresh_timeout(self, timeout: int): # We want the refresh timeout in milliseconds as an integer self._refresh_timeout = int(timeout * 1000) - - def set_on_draw_update_func(self, update_function: Callable[[],Any]): + def set_on_draw_update_func(self, update_function: Callable[[], Any]): """Adds a function that is fired during each draw call of the CUI Parameters @@ -210,8 +219,10 @@ def set_on_draw_update_func(self, update_function: Callable[[],Any]): self._on_draw_update_func = update_function - - def set_widget_cycle_key(self, forward_cycle_key: int=None, reverse_cycle_key: int=None) -> None: + def set_widget_cycle_key( + self, + forward_cycle_key: int = None, + reverse_cycle_key: int = None) -> None: """Assigns a key for automatically cycling through widgets in both focus and overview modes Parameters @@ -225,12 +236,14 @@ def set_widget_cycle_key(self, forward_cycle_key: int=None, reverse_cycle_key: i if reverse_cycle_key is not None: self._reverse_cycle_key = reverse_cycle_key - def set_toggle_live_debug_key(self, toggle_debug_key: int) -> None: self._toggle_live_debug_key = toggle_debug_key - - def enable_logging(self, log_file_path: str='py_cui.log', logging_level = logging.DEBUG, live_debug_key: int = py_cui.keys.KEY_CTRL_D) -> None: + def enable_logging( + self, + log_file_path: str = 'py_cui.log', + logging_level=logging.DEBUG, + live_debug_key: int = py_cui.keys.KEY_CTRL_D) -> None: """Function enables logging for py_cui library Parameters @@ -242,13 +255,15 @@ def enable_logging(self, log_file_path: str='py_cui.log', logging_level = loggin """ try: - py_cui.debug._enable_logging(self._logger, filename=log_file_path, logging_level=logging_level) + py_cui.debug._enable_logging( + self._logger, + filename=log_file_path, + logging_level=logging_level) self._logger.info('Initialized logger') self._toggle_live_debug_key = live_debug_key except PermissionError as e: print(f'Failed to initialize logger: {str(e)}') - def apply_widget_set(self, new_widget_set: py_cui.widget_set.WidgetSet) -> None: """Function that replaces all widgets in a py_cui with those of a different widget set @@ -265,9 +280,9 @@ def apply_widget_set(self, new_widget_set: py_cui.widget_set.WidgetSet) -> None: if isinstance(new_widget_set, py_cui.widget_set.WidgetSet): self.lose_focus() - self._widgets = new_widget_set._widgets - self._grid = new_widget_set._grid - self._keybindings = new_widget_set._keybindings + self._widgets = new_widget_set._widgets + self._grid = new_widget_set._grid + self._keybindings = new_widget_set._keybindings self._refresh_height_width() if self._stdscr is not None: @@ -276,8 +291,10 @@ def apply_widget_set(self, new_widget_set: py_cui.widget_set.WidgetSet) -> None: else: raise TypeError('Argument must be of type py_cui.widget_set.WidgetSet') - - def create_new_widget_set(self, num_rows: int, num_cols: int) -> 'py_cui.widget_set.WidgetSet': + def create_new_widget_set( + self, + num_rows: int, + num_cols: int) -> 'py_cui.widget_set.WidgetSet': """Function that is used to create additional widget sets Use this function instead of directly creating widget set object instances, to allow @@ -298,15 +315,13 @@ def create_new_widget_set(self, num_rows: int, num_cols: int) -> 'py_cui.widget_ # Use current logging object and simulated terminal for sub-widget sets return py_cui.widget_set.WidgetSet(num_rows, num_cols, self._logger, root=self, - simulated_terminal=self._simulated_terminal) - + simulated_terminal=self._simulated_terminal) # ----------------------------------------------# # Initialization functions # # Used to initialzie CUI and its features # # ----------------------------------------------# - def start(self) -> None: """Function that starts the CUI """ @@ -315,7 +330,6 @@ def start(self) -> None: self._stopped = False curses.wrapper(self._draw) - def stop(self) -> None: """Function that stops the CUI, and fires the callback function. @@ -325,8 +339,7 @@ def stop(self) -> None: self._logger.info('Stopping CUI') self._stopped = True - - def run_on_exit(self, command: Callable[[],Any]): + def run_on_exit(self, command: Callable[[], Any]): """Sets callback function on CUI exit. Must be a no-argument function or lambda function Parameters @@ -337,7 +350,6 @@ def run_on_exit(self, command: Callable[[],Any]): self._on_stop = command - def set_title(self, title: str) -> None: """Sets the title bar text @@ -349,7 +361,6 @@ def set_title(self, title: str) -> None: self._title = title - def set_status_bar_text(self, text: str) -> None: """Sets the status bar text when in overview mode @@ -362,7 +373,6 @@ def set_status_bar_text(self, text: str) -> None: self._init_status_bar_text = text self.status_bar.set_text(text) - def _initialize_colors(self) -> None: """Function for initialzing curses colors. Called when CUI is first created. """ @@ -374,7 +384,6 @@ def _initialize_colors(self) -> None: fg_color, bg_color = py_cui.colors._COLOR_MAP[color_pair] curses.init_pair(color_pair, fg_color, bg_color) - def _initialize_widget_renderer(self) -> None: """Function that creates the renderer object that will draw each widget """ @@ -387,27 +396,35 @@ def _initialize_widget_renderer(self) -> None: try: widget._assign_renderer(self._renderer) except py_cui.errors.PyCUIError: - self._logger.debug(f'Renderer already assigned for widget {self.get_widgets()[widget_id]}') + self._logger.debug( + f'Renderer already assigned for widget {self.get_widgets()[widget_id]}') try: if self._popup is not None: self._popup._assign_renderer(self._renderer) if self._logger is not None: self._logger._live_debug_element._assign_renderer(self._renderer) except py_cui.errors.PyCUIError: - self._logger.debug('Renderer already assigned to popup or live-debug elements') - + self._logger.debug( + 'Renderer already assigned to popup or live-debug elements') def toggle_unicode_borders(self) -> None: """Function for toggling unicode based border rendering """ if self._border_characters is None or self._border_characters['UP_LEFT'] == '+': - self.set_widget_border_characters('\u256d', '\u256e', '\u2570', '\u256f', '\u2500', '\u2502') + self.set_widget_border_characters( + '\u256d', '\u256e', '\u2570', '\u256f', '\u2500', '\u2502') else: self.set_widget_border_characters('+', '+', '+', '+', '-', '|') - - def set_widget_border_characters(self, upper_left_corner: str, upper_right_corner: str, lower_left_corner: str, lower_right_corner: str, horizontal: str, vertical: str) -> None: + def set_widget_border_characters( + self, + upper_left_corner: str, + upper_right_corner: str, + lower_left_corner: str, + lower_right_corner: str, + horizontal: str, + vertical: str) -> None: """Function that can be used to set arbitrary border characters for drawing widget borders by renderer. Parameters @@ -436,8 +453,7 @@ def set_widget_border_characters(self, upper_left_corner: str, upper_right_corne } self._logger.debug(f'Set border_characters to {self._border_characters}') - - def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']]: + def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']]: """Function that gets current set of widgets Returns @@ -451,7 +467,15 @@ def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']]: # Widget add functions. Each of these adds a particular type of widget # to the grid in a specified location. - def add_scroll_menu(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0) -> 'py_cui.widgets.ScrollMenu': + def add_scroll_menu( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0) -> 'py_cui.widgets.ScrollMenu': """Function that adds a new scroll menu to the CUI grid Parameters @@ -490,14 +514,23 @@ def add_scroll_menu(self, title: str, row: int, column: int, row_span: int=1, co self._logger) if self._renderer is not None: new_scroll_menu._assign_renderer(self._renderer) - self.get_widgets()[id] = new_scroll_menu + self.get_widgets()[id] = new_scroll_menu if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}') + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}') return new_scroll_menu - - def add_checkbox_menu(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0, checked_char: str='X') -> 'py_cui.widgets.CheckBoxMenu': + def add_checkbox_menu( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + checked_char: str = 'X') -> 'py_cui.widgets.CheckBoxMenu': """Function that adds a new checkbox menu to the CUI grid Parameters @@ -539,14 +572,24 @@ def add_checkbox_menu(self, title: str, row: int, column: int, row_span: int=1, checked_char) if self._renderer is not None: new_checkbox_menu._assign_renderer(self._renderer) - self.get_widgets()[id] = new_checkbox_menu + self.get_widgets()[id] = new_checkbox_menu if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}') + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}') return new_checkbox_menu - - def add_text_box(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, initial_text: str = '', password: bool = False) -> 'py_cui.widgets.TextBox': + def add_text_box( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + initial_text: str = '', + password: bool = False) -> 'py_cui.widgets.TextBox': """Function that adds a new text box to the CUI grid Parameters @@ -589,14 +632,23 @@ def add_text_box(self, title: str, row: int, column: int, row_span: int = 1, col password) if self._renderer is not None: new_text_box._assign_renderer(self._renderer) - self.get_widgets()[id] = new_text_box + self.get_widgets()[id] = new_text_box if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}') + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}') return new_text_box - - def add_text_block(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, initial_text: str = '') -> 'py_cui.widgets.ScrollTextBlock': + def add_text_block( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + initial_text: str = '') -> 'py_cui.widgets.ScrollTextBlock': """Function that adds a new text block to the CUI grid Parameters @@ -638,14 +690,22 @@ def add_text_block(self, title: str, row: int, column: int, row_span: int = 1, c initial_text) if self._renderer is not None: new_text_block._assign_renderer(self._renderer) - self.get_widgets()[id] = new_text_block + self.get_widgets()[id] = new_text_block if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}') + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}') return new_text_block - - def add_label(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0) -> 'py_cui.widgets.Label': + def add_label( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0) -> 'py_cui.widgets.Label': """Function that adds a new label to the CUI grid Parameters @@ -684,12 +744,21 @@ def add_label(self, title: str, row: int, column: int, row_span: int = 1, column self._logger) if self._renderer is not None: new_label._assign_renderer(self._renderer) - self.get_widgets()[id] = new_label - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') + self.get_widgets()[id] = new_label + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') return new_label - - def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, center: bool=True) -> 'py_cui.widgets.BlockLabel': + def add_block_label( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + center: bool = True) -> 'py_cui.widgets.BlockLabel': """Function that adds a new block label to the CUI grid Parameters @@ -731,12 +800,22 @@ def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, self._logger) if self._renderer is not None: new_label._assign_renderer(self._renderer) - self.get_widgets()[id] = new_label - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') + self.get_widgets()[id] = new_label + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') return new_label - - def add_button(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, command: Callable[[],Any]=None) -> 'py_cui.widgets.Button': + def add_button( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + command: Callable[[], + Any] = None) -> 'py_cui.widgets.Button': """Function that adds a new button to the CUI grid Parameters @@ -778,16 +857,26 @@ def add_button(self, title: str, row: int, column: int, row_span: int = 1, colum command) if self._renderer is not None: new_button._assign_renderer(self._renderer) - self.get_widgets()[id] = new_button + self.get_widgets()[id] = new_button if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') return new_button - - def add_slider(self, title: str, row: int, column: int, row_span: int=1, - column_span: int=1, padx: int=1, pady: int=0, - min_val: int=0, max_val: int=100, step: int=1, init_val: int=0) -> 'py_cui.controls.slider.SliderWidget': + def add_slider( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + min_val: int = 0, + max_val: int = 100, + step: int = 1, + init_val: int = 0) -> 'py_cui.controls.slider.SliderWidget': """Function that adds a new label to the CUI grid Parameters @@ -839,11 +928,11 @@ def add_slider(self, title: str, row: int, column: int, row_span: int=1, if self._renderer is not None: new_slider._assign_renderer(self._renderer) self.get_widgets()[id] = new_slider - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}') + self._logger.info( + f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}') return new_slider - - def forget_widget(self, widget : 'py_cui.widgets.Widget') -> None: + def forget_widget(self, widget: 'py_cui.widgets.Widget') -> None: """Function that is used to destroy or "forget" widgets. Forgotten widgets will no longer be drawn Parameters @@ -862,12 +951,15 @@ def forget_widget(self, widget : 'py_cui.widgets.Widget') -> None: if not isinstance(widget, py_cui.widgets.Widget): raise TypeError('Argument widget must by of type py_cui.widgets.Widget!') elif widget.get_id() not in self.get_widgets().keys(): - raise KeyError(f'Widget with id {widget.get_id()} has already been removed from the UI!') + raise KeyError( + f'Widget with id {widget.get_id()} has already been removed from the UI!') else: self.get_widgets()[widget.get_id()] = None - - def get_element_at_position(self, x: int, y: int) -> Optional['py_cui.ui.UIElement']: + def get_element_at_position( + self, + x: int, + y: int) -> Optional['py_cui.ui.UIElement']: """Returns containing widget for character position Parameters @@ -894,8 +986,9 @@ def get_element_at_position(self, x: int, y: int) -> Optional['py_cui.ui.UIEleme return widget return None - - def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]]: + def _get_horizontal_neighbors(self, + widget: 'py_cui.widgets.Widget', + direction: int) -> Optional[List[int]]: """Gets all horizontal (left, right) neighbor widgets Parameters @@ -911,13 +1004,13 @@ def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', direction: A list of the neighbor widget ids """ - if not direction in py_cui.keys.ARROW_KEYS: + if direction not in py_cui.keys.ARROW_KEYS: return None - _, num_cols = self._grid.get_dimensions() - row_start, col_start = widget.get_grid_cell() - row_span, col_span = widget.get_grid_cell_spans() - id_list = [] + _, num_cols = self._grid.get_dimensions() + row_start, col_start = widget.get_grid_cell() + row_span, col_span = widget.get_grid_cell_spans() + id_list = [] if direction == py_cui.keys.KEY_LEFT_ARROW: col_range_start = 0 @@ -929,9 +1022,11 @@ def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', direction: for col in range(col_range_start, col_range_stop): for row in range(row_start, row_start + row_span): for widget_id in self.get_widgets().keys(): - item_value = self.get_widgets()[widget_id] #using temporary variable, for mypy + # using temporary variable, for mypy + item_value = self.get_widgets()[widget_id] if item_value is not None: - if item_value._is_row_col_inside(row, col) and widget_id not in id_list: + if item_value._is_row_col_inside( + row, col) and widget_id not in id_list: id_list.append(widget_id) if direction == py_cui.keys.KEY_LEFT_ARROW: @@ -942,8 +1037,9 @@ def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', direction: return id_list - - def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]]: + def _get_vertical_neighbors(self, + widget: 'py_cui.widgets.Widget', + direction: int) -> Optional[List[int]]: """Gets all vertical (up, down) neighbor widgets Parameters @@ -959,13 +1055,13 @@ def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', direction: in A list of the neighbor widget ids """ - if not direction in py_cui.keys.ARROW_KEYS: + if direction not in py_cui.keys.ARROW_KEYS: return None - num_rows, _ = self._grid.get_dimensions() - row_start, col_start = widget.get_grid_cell() - row_span, col_span = widget.get_grid_cell_spans() - id_list = [] + num_rows, _ = self._grid.get_dimensions() + row_start, col_start = widget.get_grid_cell() + row_span, col_span = widget.get_grid_cell_spans() + id_list = [] if direction == py_cui.keys.KEY_UP_ARROW: row_range_start = 0 @@ -979,7 +1075,8 @@ def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', direction: in for widget_id in self.get_widgets().keys(): item_value = self.get_widgets()[widget_id] if item_value is not None: - if item_value._is_row_col_inside(row, col) and widget_id not in id_list: + if item_value._is_row_col_inside( + row, col) and widget_id not in id_list: id_list.append(widget_id) if direction == py_cui.keys.KEY_UP_ARROW: @@ -1010,7 +1107,8 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: """ if self._selected_widget is not None: - start_widget: Optional[py_cui.widgets.Widget] = self.get_widgets()[self._selected_widget] + start_widget: Optional[py_cui.widgets.Widget] = self.get_widgets()[ + self._selected_widget] # Find all the widgets in the given row or column neighbors: Optional[List[int]] = [] @@ -1026,7 +1124,6 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: # We select the best match to jump to (first neighbor) return neighbors[0] - def get_selected_widget(self) -> Optional['py_cui.widgets.Widget']: """Function that gets currently selected widget @@ -1042,7 +1139,6 @@ def get_selected_widget(self) -> Optional['py_cui.widgets.Widget']: self._logger.warn('Selected widget ID is None or invalid') return None - def set_selected_widget(self, widget_id: int) -> None: """Function that sets the selected widget for the CUI @@ -1056,8 +1152,8 @@ def set_selected_widget(self, widget_id: int) -> None: self._logger.debug(f'Setting selected widget to ID {widget_id}') self._selected_widget = widget_id else: - self._logger.warn(f'Widget w/ ID {widget_id} does not exist among current widgets.') - + self._logger.warn( + f'Widget w/ ID {widget_id} does not exist among current widgets.') def lose_focus(self) -> None: """Function that forces py_cui out of focus mode. @@ -1075,8 +1171,8 @@ def lose_focus(self) -> None: else: self._logger.info('lose_focus: Not currently in focus mode') - - def move_focus(self, widget: 'py_cui.widgets.Widget', auto_press_buttons: bool=True) -> None: + def move_focus(self, widget: 'py_cui.widgets.Widget', + auto_press_buttons: bool = True) -> None: """Moves focus mode to different widget Parameters @@ -1088,12 +1184,15 @@ def move_focus(self, widget: 'py_cui.widgets.Widget', auto_press_buttons: bool=T self.lose_focus() self.set_selected_widget(widget.get_id()) - # If autofocus buttons is selected, we automatically process the button command and reset to overview mode - if self._auto_focus_buttons and auto_press_buttons and isinstance(widget, py_cui.widgets.Button): + # If autofocus buttons is selected, we automatically process the button + # command and reset to overview mode + if self._auto_focus_buttons and auto_press_buttons and isinstance( + widget, py_cui.widgets.Button): if widget.command is not None: widget.command() - self._logger.debug(f'Moved focus to button {widget.get_title()} - ran autofocus command') + self._logger.debug( + f'Moved focus to button {widget.get_title()} - ran autofocus command') elif self._auto_focus_buttons and isinstance(widget, py_cui.widgets.Button): self.status_bar.set_text(self._init_status_bar_text) @@ -1104,8 +1203,7 @@ def move_focus(self, widget: 'py_cui.widgets.Widget', auto_press_buttons: bool=T self._logger.debug(f'Moved focus to widget {widget.get_title()}') - - def _cycle_widgets(self, reverse: bool=False) -> None: + def _cycle_widgets(self, reverse: bool = False) -> None: """Function that is fired if cycle key is pressed to move to next widget Parameters @@ -1137,15 +1235,16 @@ def _cycle_widgets(self, reverse: bool=False) -> None: next_widget_id: int = next_widget_num current_widget = self.get_widgets()[current_widget_id] next_widget = self.get_widgets()[next_widget_id] - if current_widget and next_widget is not None: #pls check again + if current_widget and next_widget is not None: # pls check again if self._in_focused_mode and cycle_key in current_widget._key_commands.keys(): - # In the event that we are focusing on a widget with that key defined, we do not cycle. + # In the event that we are focusing on a widget with that key defined, + # we do not cycle. pass else: self.move_focus(next_widget, auto_press_buttons=False) - - def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) -> None: + def add_key_command(self, key: Union[int, List[int]], + command: Callable[[], Any]) -> None: """Function that adds a keybinding to the CUI when in overview mode Parameters @@ -1164,7 +1263,11 @@ def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) # Popup functions. Used to display messages, warnings, and errors to the user. - def show_message_popup(self, title: str, text: str, color: int = WHITE_ON_BLACK) -> None: + def show_message_popup( + self, + title: str, + text: str, + color: int = WHITE_ON_BLACK) -> None: """Shows a message popup Parameters @@ -1177,10 +1280,10 @@ def show_message_popup(self, title: str, text: str, color: int = WHITE_ON_BLACK) Popup color with format FOREGOUND_ON_BACKGROUND. See colors module. Default: WHITE_ON_BLACK. """ - self._popup = py_cui.popups.MessagePopup(self, title, text, color, self._renderer, self._logger) + self._popup = py_cui.popups.MessagePopup( + self, title, text, color, self._renderer, self._logger) self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_warning_popup(self, title: str, text: str) -> None: """Shows a warning popup @@ -1194,7 +1297,6 @@ def show_warning_popup(self, title: str, text: str) -> None: self.show_message_popup(title=title, text=text, color=YELLOW_ON_BLACK) - def show_error_popup(self, title: str, text: str) -> None: """Shows an error popup @@ -1208,7 +1310,6 @@ def show_error_popup(self, title: str, text: str) -> None: self.show_message_popup(title=title, text=text, color=RED_ON_BLACK) - def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]): """Shows a yes/no popup. @@ -1223,11 +1324,18 @@ def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]): """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.YesNoPopup(self, title + '- (y/n)', 'Yes - (y), No - (n)', color, command, self._renderer, self._logger) + self._popup = py_cui.popups.YesNoPopup( + self, + title + '- (y/n)', + 'Yes - (y), No - (n)', + color, + command, + self._renderer, + self._logger) self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_text_box_popup(self, title: str, command: Callable[[str], Any], password: bool=False): + def show_text_box_popup(self, title: str, command: Callable[[ + str], Any], password: bool = False): """Shows a textbox popup. The 'command' parameter must be a function with a single string parameter @@ -1243,11 +1351,12 @@ def show_text_box_popup(self, title: str, command: Callable[[str], Any], passwor """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.TextBoxPopup(self, title, color, command, self._renderer, password, self._logger) + self._popup = py_cui.popups.TextBoxPopup( + self, title, color, command, self._renderer, password, self._logger) self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_menu_popup(self, title: str, menu_items: List[str], command: Callable[[str], Any], run_command_if_none: bool=False): + def show_menu_popup(self, title: str, menu_items: List[str], command: Callable[[ + str], Any], run_command_if_none: bool = False): """Shows a menu popup. The 'command' parameter must be a function with a single string parameter @@ -1265,11 +1374,23 @@ def show_menu_popup(self, title: str, menu_items: List[str], command: Callable[[ """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.MenuPopup(self, menu_items, title, color, command, self._renderer, self._logger, run_command_if_none) + self._popup = py_cui.popups.MenuPopup( + self, + menu_items, + title, + color, + command, + self._renderer, + self._logger, + run_command_if_none) self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_loading_icon_popup(self, title: str, message: str, callback: Callable[[],Any]=None): + def show_loading_icon_popup( + self, + title: str, + message: str, + callback: Callable[[], + Any] = None): """Shows a loading icon popup Parameters @@ -1288,11 +1409,16 @@ def show_loading_icon_popup(self, title: str, message: str, callback: Callable[[ color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingIconPopup(self, title, message, color, self._renderer, self._logger) + self._popup = py_cui.popups.LoadingIconPopup( + self, title, message, color, self._renderer, self._logger) self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_loading_bar_popup(self, title: str, num_items: List[int], callback: Callable[[],Any]=None) -> None: + def show_loading_bar_popup( + self, + title: str, + num_items: List[int], + callback: Callable[[], + Any] = None) -> None: """Shows loading bar popup. Use 'increment_loading_bar' to show progress @@ -1313,11 +1439,18 @@ def show_loading_bar_popup(self, title: str, num_items: List[int], callback: Cal color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingBarPopup(self, title, num_items, color, self._renderer, self._logger) + self._popup = py_cui.popups.LoadingBarPopup( + self, title, num_items, color, self._renderer, self._logger) self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_form_popup(self, title: str, fields: List[str], passwd_fields: List[str]=[], required: List[str]=[], callback: Callable[[],Any]=None) -> None: + def show_form_popup( + self, + title: str, + fields: List[str], + passwd_fields: List[str] = [], + required: List[str] = [], + callback: Callable[[], + Any] = None) -> None: """Shows form popup. Used for inputting several fields worth of values @@ -1352,8 +1485,14 @@ def show_form_popup(self, title: str, fields: List[str], passwd_fields: List[str self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_filedialog_popup(self, popup_type: str='openfile', initial_dir: str ='.', callback: Callable[[],Any]=None, ascii_icons: bool=True, limit_extensions: List[str]=[]) -> None: + def show_filedialog_popup( + self, + popup_type: str = 'openfile', + initial_dir: str = '.', + callback: Callable[[], + Any] = None, + ascii_icons: bool = True, + limit_extensions: List[str] = []) -> None: """Shows form popup. Used for inputting several fields worth of values @@ -1382,8 +1521,8 @@ def show_filedialog_popup(self, popup_type: str='openfile', initial_dir: str ='. self._renderer, self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with type {popup_type}') - + self._logger.debug( + f'Opened {str(type(self._popup))} popup with type {popup_type}') def increment_loading_bar(self) -> None: """Increments progress bar if loading bar popup is open @@ -1394,7 +1533,6 @@ def increment_loading_bar(self) -> None: else: self._logger.warn('No popup is currently opened.') - def stop_loading_popup(self) -> None: """Leaves loading state, and closes popup. @@ -1405,7 +1543,6 @@ def stop_loading_popup(self) -> None: self.close_popup() self._logger.debug('Stopping open loading popup') - def close_popup(self) -> None: """Closes the popup, and resets focus """ @@ -1413,31 +1550,30 @@ def close_popup(self) -> None: self.lose_focus() self._popup = None - def _refresh_height_width(self) -> None: """Function that updates the height and width of the CUI based on terminal window size.""" if self._simulated_terminal is None: if self._stdscr is None: term_size = shutil.get_terminal_size() - height = term_size.lines - width = term_size.columns + height = term_size.lines + width = term_size.columns else: # Use curses termsize when possible to fix resize bug on windows. height, width = self._stdscr.getmaxyx() else: - height = self._simulated_terminal[0] - width = self._simulated_terminal[1] + height = self._simulated_terminal[0] + width = self._simulated_terminal[1] - height = height - self.title_bar.get_height() - self.status_bar.get_height() - 2 + height = height - self.title_bar.get_height() - self.status_bar.get_height() - 2 self._logger.debug(f'Resizing CUI to new dimensions {height} by {width}') self._height = height - self._width = width + self._width = width self._grid.update_grid_height_width(self._height, self._width) for widget_id in self.get_widgets().keys(): - widget = self.get_widgets()[widget_id] #using temp variable, for mypy + widget = self.get_widgets()[widget_id] # using temp variable, for mypy if widget is not None: widget.update_height_width() if self._popup is not None: @@ -1445,7 +1581,7 @@ def _refresh_height_width(self) -> None: if self._logger._live_debug_element is not None: self._logger._live_debug_element.update_height_width() - def get_absolute_size(self) -> Tuple[int,int]: + def get_absolute_size(self) -> Tuple[int, int]: """Returns dimensions of CUI Returns @@ -1479,7 +1615,6 @@ def _draw_widgets(self) -> None: self._logger.info('Drew widgets') - def _draw_status_bars(self, stdscr, height: int, width: int) -> None: """Draws status bar and title bar @@ -1503,7 +1638,6 @@ def _draw_status_bars(self, stdscr, height: int, width: int) -> None: stdscr.addstr(0, 0, fit_text(width, self._title, center=True)) stdscr.attroff(curses.color_pair(self.title_bar.get_color())) - def _display_window_warning(self, stdscr, error_info: str) -> None: """Function that prints some basic error info if there is an error with the CUI @@ -1524,7 +1658,6 @@ def _display_window_warning(self, stdscr, error_info: str) -> None: stdscr.refresh() self._logger.error(f'Encountered error -> {error_info}') - def _handle_key_presses(self, key_pressed: int) -> None: """Function that handles all main loop key presses. @@ -1534,7 +1667,8 @@ def _handle_key_presses(self, key_pressed: int) -> None: The key being pressed """ - # Selected widget represents which widget is being hovered over, though not necessarily in focus mode + # Selected widget represents which widget is being hovered over, though + # not necessarily in focus mode if self._selected_widget is None: return @@ -1547,7 +1681,8 @@ def _handle_key_presses(self, key_pressed: int) -> None: if key_pressed == self._toggle_live_debug_key: self._logger.toggle_live_debug() - # If we are in live debug mode, we only handle keypresses for the live debug UI element + # If we are in live debug mode, we only handle keypresses for the live + # debug UI element if self._logger is not None and self._logger.is_live_debug_enabled(): self._logger._live_debug_element._handle_key_press(key_pressed) @@ -1558,13 +1693,16 @@ def _handle_key_presses(self, key_pressed: int) -> None: self.status_bar.set_text(self._init_status_bar_text) self._in_focused_mode = False selected_widget.set_selected(False) - self._logger.debug(f'Exiting focus mode on widget {selected_widget.get_title()}') + self._logger.debug( + f'Exiting focus mode on widget {selected_widget.get_title()}') else: # widget handles remaining py_cui.keys - self._logger.debug(f'Widget {selected_widget.get_title()} handling {key_pressed} key') + self._logger.debug( + f'Widget {selected_widget.get_title()} handling {key_pressed} key') 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 + # 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.move_focus(selected_widget) @@ -1572,10 +1710,12 @@ def _handle_key_presses(self, key_pressed: int) -> None: for key in self._keybindings.keys(): if key_pressed == key: command = self._keybindings[key] - self._logger.info(f'Detected binding for key {key_pressed}, running command {command.__name__}') + self._logger.info( + f'Detected binding for key {key_pressed}, running command {command.__name__}') command() - # If not in focus mode, use the arrow py_cui.keys to move around the selectable widgets. + # If not in focus mode, use the arrow py_cui.keys to move around the + # selectable widgets. neighbor = None if key_pressed in py_cui.keys.ARROW_KEYS: neighbor = self._check_if_neighbor_exists(key_pressed) @@ -1583,14 +1723,15 @@ def _handle_key_presses(self, key_pressed: int) -> None: self.set_selected_widget(neighbor) widget = self.get_widgets()[self._selected_widget] if widget is not None: - self._logger.debug(f'Navigated to neighbor widget {widget.get_title()}') + self._logger.debug( + f'Navigated to neighbor widget {widget.get_title()}') # if we have a popup, that takes key control from both overview and focus mode elif self._popup is not None: - self._logger.debug(f'Popup {self._popup.get_title()} handling key {key_pressed}') + self._logger.debug( + f'Popup {self._popup.get_title()} handling key {key_pressed}') self._popup._handle_key_press(key_pressed) - def _draw(self, stdscr) -> None: """Main CUI draw loop called by start() @@ -1608,7 +1749,7 @@ def _draw(self, stdscr) -> None: stdscr.refresh() curses.mousemask(curses.ALL_MOUSE_EVENTS) # stdscr.nodelay(False) - #stdscr.keypad(True) + # stdscr.keypad(True) # Initialization functions. Generates colors and renderer self._initialize_colors() @@ -1622,7 +1763,8 @@ def _draw(self, stdscr) -> None: if self._border_characters is not None and self._renderer is not None: 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 + # 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: try: @@ -1648,7 +1790,8 @@ def _draw(self, stdscr) -> None: self._logger.info('Resized terminal too small') self._display_window_warning(stdscr, str(e)) - # Here we handle mouse click events globally, or pass them to the UI element to handle + # Here we handle mouse click events globally, or pass them to the UI + # element to handle elif key_pressed == curses.KEY_MOUSE: self._logger.info('Detected mouse click') @@ -1662,9 +1805,11 @@ def _draw(self, stdscr) -> None: if valid_mouse_event: in_element = self.get_element_at_position(x, y) - # In first case, we click inside already selected widget, pass click for processing + # In first case, we click inside already selected widget, pass + # click for processing if in_element is not None: - self._logger.info(f'handling mouse press for elem: {in_element.get_title()}') + self._logger.info( + f'handling mouse press for elem: {in_element.get_title()}') in_element._handle_mouse_press(x, y, mouse_event) # Otherwise, if not a popup, select the clicked on widget @@ -1674,7 +1819,8 @@ def _draw(self, stdscr) -> None: # If we have a post_loading_callback, fire it here if self._post_loading_callback is not None and not self._loading: - self._logger.debug(f'Firing post-loading callback function {self._post_loading_callback.__name__}') + self._logger.debug( + f'Firing post-loading callback function {self._post_loading_callback.__name__}') self._post_loading_callback() self._post_loading_callback = None @@ -1688,7 +1834,8 @@ def _draw(self, stdscr) -> None: self._handle_key_presses(key_pressed) try: - # Draw status/title bar, and all widgets. Selected widget will be bolded. + # Draw status/title bar, and all widgets. Selected widget will be + # bolded. self._draw_status_bars(stdscr, self._height, self._width) self._draw_widgets() # draw the popup if required @@ -1713,7 +1860,8 @@ def _draw(self, stdscr) -> None: 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. + # 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 @@ -1725,7 +1873,6 @@ def _draw(self, stdscr) -> None: self._logger.info('Detect Keyboard Interrupt, Exiting...') self._stopped = True - stdscr.erase() stdscr.refresh() curses.endwin() @@ -1733,7 +1880,6 @@ def _draw(self, stdscr) -> None: self._logger.debug(f'Firing onstop function {self._on_stop.__name__}') self._on_stop() - def __format__(self, fmt): """Override of base format function. Prints list of current widgets. From cae3fe715451135aa74e00c2df9e5c7d9a42e77d Mon Sep 17 00:00:00 2001 From: PabloLec Date: Tue, 9 Nov 2021 13:50:47 +0100 Subject: [PATCH 08/30] Fix widget `add_key_command` --- py_cui/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py_cui/widgets.py b/py_cui/widgets.py index bb8991a..4327fd0 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -97,9 +97,9 @@ def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) if isinstance(key, list): for value in key: - self._keybindings[value] = command + self._key_commands[value] = command else: - self._keybindings[key] = command + self._key_commands[key] = command def add_mouse_command(self, mouse_event: int, command: Callable[[],Any]) -> None: From 738c96e6d673443be0b044958b06f087925666b1 Mon Sep 17 00:00:00 2001 From: Pablo Lecolinet Date: Mon, 29 Nov 2021 09:58:19 +0100 Subject: [PATCH 09/30] Update setup.py Typo fix --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d93f979..66af51b 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ description='A widget and grid based framework for building command line user interfaces in python.', long_description=long_description, long_description_content_type='text/markdown', - version='0.1.5, + version='0.1.5', author='Jakub Wlodek', author_email='jwlodek.dev@gmail.com', license='BSD (3-clause)', From 5a911fc62c4f920b78f93f062a4fa32eae144161 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 4 Dec 2021 09:39:02 +0100 Subject: [PATCH 10/30] Fix `_cycle_widgets` method --- py_cui/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 0875c9a..ef3d896 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1120,14 +1120,14 @@ def _cycle_widgets(self, reverse: bool=False) -> None: if current_widget_num is not None: if not reverse: next_widget_num = current_widget_num + 1 - if self.get_widgets()[next_widget_num] is None: + if self.get_widgets().get(next_widget_num) is None: if next_widget_num == num_widgets: next_widget_num = 0 next_widget_num = next_widget_num + 1 cycle_key = self._forward_cycle_key else: next_widget_num = current_widget_num - 1 - if self.get_widgets()[next_widget_num] is None: + if self.get_widgets().get(next_widget_num) is None: if next_widget_num < 0: next_widget_num = num_widgets - 1 next_widget_num = next_widget_num + 1 From f0e860d34b3e93cb8ec58ce581ccbf821493851a Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 4 Dec 2021 10:08:10 +0100 Subject: [PATCH 11/30] Refactor `_cycle_widget` --- py_cui/__init__.py | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index ef3d896..ebd82ef 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1114,35 +1114,31 @@ def _cycle_widgets(self, reverse: bool=False) -> None: Default false. If true, cycle widgets in reverse order. """ - num_widgets = len(self.get_widgets().keys()) + num_widgets: int = len(self.get_widgets()) current_widget_num: Optional[int] = self._selected_widget - if current_widget_num is not None: - if not reverse: - next_widget_num = current_widget_num + 1 - if self.get_widgets().get(next_widget_num) is None: - if next_widget_num == num_widgets: - next_widget_num = 0 - next_widget_num = next_widget_num + 1 - cycle_key = self._forward_cycle_key - else: - next_widget_num = current_widget_num - 1 - if self.get_widgets().get(next_widget_num) is None: - if next_widget_num < 0: - next_widget_num = num_widgets - 1 - next_widget_num = next_widget_num + 1 - cycle_key = self._reverse_cycle_key - - current_widget_id: int = current_widget_num - next_widget_id: int = next_widget_num - current_widget = self.get_widgets()[current_widget_id] - next_widget = self.get_widgets()[next_widget_id] - if current_widget and next_widget is not None: #pls check again + if current_widget_num is None: + return + + if reverse: + next_widget_num = current_widget_num - 1 + if next_widget_num < 0: + next_widget_num = num_widgets - 1 + cycle_key = self._reverse_cycle_key + else: + next_widget_num = current_widget_num + 1 + if next_widget_num >= num_widgets: + next_widget_num = 0 + cycle_key = self._forward_cycle_key + + current_widget = self.get_widgets().get(current_widget_num) + next_widget = self.get_widgets().get(next_widget_num) + + if current_widget is not None and next_widget is not None: if self._in_focused_mode and cycle_key in current_widget._key_commands.keys(): # In the event that we are focusing on a widget with that key defined, we do not cycle. - pass - else: - self.move_focus(next_widget, auto_press_buttons=False) + return + self.move_focus(next_widget, auto_press_buttons=False) def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) -> None: From e25d73221fd969c759c6aa72aeac4fd43aae3042 Mon Sep 17 00:00:00 2001 From: Voryzen Date: Sat, 4 Dec 2021 23:08:31 +1000 Subject: [PATCH 12/30] Fixes docs formatting for py_cui API Commenting was placed erroneously, - fixed Formatting for 'get_absolute_size' heading added Added 'get_absolute_size' to list at beginning of doc --- docs/DocstringGenerated/PyCui.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/DocstringGenerated/PyCui.md b/docs/DocstringGenerated/PyCui.md index f59d912..f4c44c6 100644 --- a/docs/DocstringGenerated/PyCui.md +++ b/docs/DocstringGenerated/PyCui.md @@ -132,7 +132,8 @@ first create an instance of this class, and then add cells + widgets to it. increment_loading_bar | Increments progress bar if loading bar popup is open stop_loading_popup | Leaves loading state, and closes popup. close_popup | Closes the popup, and resets focus - _refresh_height_width | Function that updates the height and width of the CUI based on terminal window size.""" + _refresh_height_width | Function that updates the height and width of the CUI based on terminal window size. + get_absolute_size | Returns dimensions of CUI _draw_widgets | Function that draws all of the widgets to the screen _draw_status_bars | Draws status bar and title bar _display_window_warning | Function that prints some basic error info if there is an error with the CUI @@ -1327,10 +1328,10 @@ Closes the popup, and resets focus def _refresh_height_width(self) -> None ``` -Function that updates the height and width of the CUI based on terminal window size.""" - +Function that updates the height and width of the CUI based on terminal window size. +""" if self._simulated_terminal is None: if self._stdscr is None: term_size = shutil.get_terminal_size() @@ -1358,9 +1359,23 @@ if self._popup is not None: self._popup.update_height_width() if self._logger._live_debug_element is not None: self._logger._live_debug_element.update_height_width() +""" + + + + + + +### get_absolute_size + +```python def get_absolute_size(self) -> Tuple[int,int]: -"""Returns dimensions of CUI +``` + +Returns dimensions of CUI + + #### Returns From c85f0c6581fd62909f0c8facc5af3a3f29b93d6e Mon Sep 17 00:00:00 2001 From: Voryzen Date: Mon, 6 Dec 2021 15:06:58 +1000 Subject: [PATCH 13/30] Update PyCui.md --- docs/DocstringGenerated/PyCui.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DocstringGenerated/PyCui.md b/docs/DocstringGenerated/PyCui.md index f4c44c6..e26255a 100644 --- a/docs/DocstringGenerated/PyCui.md +++ b/docs/DocstringGenerated/PyCui.md @@ -139,7 +139,7 @@ first create an instance of this class, and then add cells + widgets to it. _display_window_warning | Function that prints some basic error info if there is an error with the CUI _handle_key_presses | Function that handles all main loop key presses. _draw | Main CUI draw loop called by start() - __format__ | Override of base format function. Prints list of current widgets. + format | Override of base format function. Prints list of current widgets. From ef1d3d58f7dd49f115be5726361df8572526b388 Mon Sep 17 00:00:00 2001 From: Voryzen Date: Mon, 6 Dec 2021 15:15:45 +1000 Subject: [PATCH 14/30] Update Ui.md --- docs/DocstringGenerated/Ui.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/DocstringGenerated/Ui.md b/docs/DocstringGenerated/Ui.md index 0cc03ba..3f1d1fa 100644 --- a/docs/DocstringGenerated/Ui.md +++ b/docs/DocstringGenerated/Ui.md @@ -1099,7 +1099,8 @@ Analogous to a RadioButton -----|----- clear | Clears all items from the Scroll Menu set_on_selection_change_event | Function that sets the function fired when menu selection changes. - _process_selection_change_event | Function that executes on-selection change event either with the current menu item, or with no-args""" + _process_selection_change_event | Function that executes on-selection change event either with the current menu item, or with no-args + get_selected_item_index | Gets the currently selected item set_selected_item_index | Sets the currently selected item _scroll_up | Function that scrolls the view up in the scroll menu _scroll_down | Function that scrolls the view down in the scroll menu @@ -1181,10 +1182,10 @@ Event function must take 0 or 1 parameters. If 1 parameter, the new selcted item def _process_selection_change_event(self) ``` -Function that executes on-selection change event either with the current menu item, or with no-args""" - +Function that executes on-selection change event either with the current menu item, or with no-args +""" # Identify num of args from callable. This allows for user to create commands that take in x, y # coords of the mouse press as input num_args = 0 @@ -1203,10 +1204,19 @@ elif num_args == 0: self._on_selection_change() else: raise ValueError('On selection change event must accept either 0 or 1 parameters!') +""" + + + + +### get_selected_item_index +```python def get_selected_item_index(self) -> int: -"""Gets the currently selected item +``` + +Gets the currently selected item #### Returns @@ -1219,6 +1229,7 @@ def get_selected_item_index(self) -> int: + ### set_selected_item_index ```python From 1c3d7fb875f52ec3a249ca75bd85daa5bfbe075f Mon Sep 17 00:00:00 2001 From: Voryzen Date: Mon, 6 Dec 2021 15:17:18 +1000 Subject: [PATCH 15/30] Update Ui.md --- docs/DocstringGenerated/Ui.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/DocstringGenerated/Ui.md b/docs/DocstringGenerated/Ui.md index 3f1d1fa..b3d6087 100644 --- a/docs/DocstringGenerated/Ui.md +++ b/docs/DocstringGenerated/Ui.md @@ -1185,7 +1185,7 @@ def _process_selection_change_event(self) Function that executes on-selection change event either with the current menu item, or with no-args -""" +``` # Identify num of args from callable. This allows for user to create commands that take in x, y # coords of the mouse press as input num_args = 0 @@ -1204,7 +1204,7 @@ elif num_args == 0: self._on_selection_change() else: raise ValueError('On selection change event must accept either 0 or 1 parameters!') -""" +``` From 239b39337862eb692330df79021a1b8d73c602e0 Mon Sep 17 00:00:00 2001 From: Voryzen Date: Mon, 6 Dec 2021 15:18:54 +1000 Subject: [PATCH 16/30] Update PyCui.md --- docs/DocstringGenerated/PyCui.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/DocstringGenerated/PyCui.md b/docs/DocstringGenerated/PyCui.md index e26255a..dc84342 100644 --- a/docs/DocstringGenerated/PyCui.md +++ b/docs/DocstringGenerated/PyCui.md @@ -1331,7 +1331,7 @@ def _refresh_height_width(self) -> None Function that updates the height and width of the CUI based on terminal window size. -""" +``` if self._simulated_terminal is None: if self._stdscr is None: term_size = shutil.get_terminal_size() @@ -1359,7 +1359,7 @@ if self._popup is not None: self._popup.update_height_width() if self._logger._live_debug_element is not None: self._logger._live_debug_element.update_height_width() -""" +``` From e0e20951c39cc7378256d02b8cdda6d5956fdfa1 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Wed, 29 Dec 2021 15:20:12 -0500 Subject: [PATCH 17/30] Add function for checking if menu widgets are empty --- py_cui/ui.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/py_cui/ui.py b/py_cui/ui.py index 66445a0..e5f6243 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -925,6 +925,18 @@ def get(self) -> Optional[Any]: return None + def is_empty(self) -> bool: + """Function that returns true if menu has no items within it, false otherwise + + Returns + ------- + bool + True if menu has no items, False otherwise. Identical to len(self._view_items) == 0 + """ + + return self._view_items == 0 + + def set_selected_item(self, selected_item: Any): """Function that replaces the currently selected item with a new item From 75da48fbe7090bed7a0a6cd4a3e201590ece87b4 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Wed, 29 Dec 2021 15:27:31 -0500 Subject: [PATCH 18/30] Add unit tests, add missing len function call to is_empty --- py_cui/ui.py | 2 +- tests/test_ui_implementations/test_checkbox_imp.py | 8 ++++++++ tests/test_ui_implementations/test_scroll_menu_imp.py | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/py_cui/ui.py b/py_cui/ui.py index e5f6243..a5284ce 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -934,7 +934,7 @@ def is_empty(self) -> bool: True if menu has no items, False otherwise. Identical to len(self._view_items) == 0 """ - return self._view_items == 0 + return len(self._view_items) == 0 def set_selected_item(self, selected_item: Any): diff --git a/tests/test_ui_implementations/test_checkbox_imp.py b/tests/test_ui_implementations/test_checkbox_imp.py index e0a6596..0902c2b 100644 --- a/tests/test_ui_implementations/test_checkbox_imp.py +++ b/tests/test_ui_implementations/test_checkbox_imp.py @@ -5,6 +5,14 @@ elems = ["Elem0", "Elem1", "Elem2", "Elem3", "Elem4"] +def test_is_empty(CHECKBOXMENU): + scroll = CHECKBOXMENU + assert scroll.is_empty() + scroll.add_item_list(elems) + assert not scroll.is_empty() + scroll.clear() + + def test_add_item_list(CHECKBOXMENU): scroll = CHECKBOXMENU scroll.add_item_list(elems) diff --git a/tests/test_ui_implementations/test_scroll_menu_imp.py b/tests/test_ui_implementations/test_scroll_menu_imp.py index 89a0fba..694eba3 100644 --- a/tests/test_ui_implementations/test_scroll_menu_imp.py +++ b/tests/test_ui_implementations/test_scroll_menu_imp.py @@ -6,6 +6,14 @@ elems = ["Elem0", "Elem1", "Elem2", "Elem3", "Elem4"] +def test_is_empty(SCROLLMENU): + scroll = SCROLLMENU + assert scroll.is_empty() + scroll.add_item_list(elems) + assert not scroll.is_empty() + scroll.clear() + + def test_add_item_list(SCROLLMENU): scroll = SCROLLMENU scroll.add_item_list(elems) From e0c271bb751d133e8deb164b26f9ab3bede54cf6 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Wed, 29 Dec 2021 16:02:37 -0500 Subject: [PATCH 19/30] Add add_custom_widget functions, remove lots of redundant code --- py_cui/__init__.py | 264 +++++++++++++++++++----------------------- py_cui/widget_set.py | 266 ++++++++++++++++++++----------------------- py_cui/widgets.py | 3 +- 3 files changed, 245 insertions(+), 288 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index ebd82ef..3dff5a0 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -451,6 +451,52 @@ def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']]: # Widget add functions. Each of these adds a particular type of widget # to the grid in a specified location. + def add_custom_widget(self, widget_class: type, title: str, row: int, column: int, row_span: int, column_span: int, padx: int, pady: int, *args, **kwargs) -> 'py_cui.widgets.Widget': + """Function that allows for adding custom widget types to the CUI - specifically ones not included with py_cui by default + + Parameters + ---------- + widget_class : type + The class type of your custom widget. Note that it must be a subclass of the widget superclass + title : str + The title of the scroll menu + row : int + The row value, from the top down + column : int + The column value from the top down + row_span=1 : int + The number of rows to span accross + column_span=1 : int + the number of columns to span accross + padx=1 : int + number of padding characters in the x direction + pady=0 : int + number of padding characters in the y direction + + Raises + ------ + TypeError + If provided widget class is not a subclass of widget, a typeerror is raised. + """ + + if not issubclass(widget_class, py_cui.widgets.Widget): + raise TypeError(f'Widget class {widget_class} is not a subclass of the base Widget class!') + + id = len(self.get_widgets().keys()) + new_widget = widget_class(id, title, self._grid, row, column, row_span, column_span, padx, pady, self._logger, *args, **kwargs) + if self._renderer is not None: + new_widget._assign_renderer(self._renderer) + + self.get_widgets()[id] = new_widget + + if self._selected_widget is None: + self.set_selected_widget(id) + + self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(widget_class)}') + return new_widget + + + def add_scroll_menu(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0) -> 'py_cui.widgets.ScrollMenu': """Function that adds a new scroll menu to the CUI grid @@ -477,24 +523,14 @@ def add_scroll_menu(self, title: str, row: int, column: int, row_span: int=1, co A reference to the created scroll menu object. """ - id = len(self.get_widgets().keys()) - new_scroll_menu = py_cui.widgets.ScrollMenu(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger) - if self._renderer is not None: - new_scroll_menu._assign_renderer(self._renderer) - self.get_widgets()[id] = new_scroll_menu - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}') - return new_scroll_menu + return self.add_custom_widget(py_cui.widgets.ScrollMenu, + title, + row, + column, + row_span, + column_span, + padx, + pady) def add_checkbox_menu(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0, checked_char: str='X') -> 'py_cui.widgets.CheckBoxMenu': @@ -525,25 +561,15 @@ def add_checkbox_menu(self, title: str, row: int, column: int, row_span: int=1, A reference to the created checkbox object. """ - id = len(self.get_widgets().keys()) - new_checkbox_menu = py_cui.widgets.CheckBoxMenu(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - checked_char) - if self._renderer is not None: - new_checkbox_menu._assign_renderer(self._renderer) - self.get_widgets()[id] = new_checkbox_menu - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}') - return new_checkbox_menu + return self.add_custom_widget(py_cui.widgets.CheckBoxMenu, + title, + row, + column, + row_span, + column_span, + padx, + pady, + checked_char) def add_text_box(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, initial_text: str = '', password: bool = False) -> 'py_cui.widgets.TextBox': @@ -576,24 +602,14 @@ def add_text_box(self, title: str, row: int, column: int, row_span: int = 1, col A reference to the created textbox object. """ - id = len(self.get_widgets().keys()) - new_text_box = py_cui.widgets.TextBox(id, - title, - self._grid, - row, column, - row_span, - column_span, - padx, pady, - self._logger, - initial_text, - password) - if self._renderer is not None: - new_text_box._assign_renderer(self._renderer) - self.get_widgets()[id] = new_text_box - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}') - return new_text_box + return self.add_custom_widget(py_cui.widgets.TextBox, + title, + row, column, + row_span, + column_span, + padx, pady, + initial_text, + password) def add_text_block(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, initial_text: str = '') -> 'py_cui.widgets.ScrollTextBlock': @@ -624,25 +640,15 @@ def add_text_block(self, title: str, row: int, column: int, row_span: int = 1, c A reference to the created textblock object. """ - id = len(self.get_widgets().keys()) - new_text_block = py_cui.widgets.ScrollTextBlock(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - initial_text) - if self._renderer is not None: - new_text_block._assign_renderer(self._renderer) - self.get_widgets()[id] = new_text_block - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}') - return new_text_block + return self.add_custom_widget(py_cui.widgets.ScrollTextBlock, + title, + row, + column, + row_span, + column_span, + padx, + pady, + initial_text) def add_label(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0) -> 'py_cui.widgets.Label': @@ -671,22 +677,14 @@ def add_label(self, title: str, row: int, column: int, row_span: int = 1, column A reference to the created label object. """ - id = len(self.get_widgets().keys()) - new_label = py_cui.widgets.Label(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger) - if self._renderer is not None: - new_label._assign_renderer(self._renderer) - self.get_widgets()[id] = new_label - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') - return new_label + return self.add_custom_widget(py_cui.widgets.Label, + title, + row, + column, + row_span, + column_span, + padx, + pady) def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, center: bool=True) -> 'py_cui.widgets.BlockLabel': @@ -717,23 +715,15 @@ def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, A reference to the created block label object. """ - id = len(self.get_widgets().keys()) - new_label = py_cui.widgets.BlockLabel(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - center, - self._logger) - if self._renderer is not None: - new_label._assign_renderer(self._renderer) - self.get_widgets()[id] = new_label - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') - return new_label + return self.add_custom_widget(py_cui.widgets.BlockLabel, + title, + row, + column, + row_span, + column_span, + padx, + pady, + center) def add_button(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, command: Callable[[],Any]=None) -> 'py_cui.widgets.Button': @@ -764,25 +754,15 @@ def add_button(self, title: str, row: int, column: int, row_span: int = 1, colum A reference to the created button object. """ - id = len(self.get_widgets().keys()) - new_button = py_cui.widgets.Button(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - command) - if self._renderer is not None: - new_button._assign_renderer(self._renderer) - self.get_widgets()[id] = new_button - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') - return new_button + self.add_custom_widget(py_cui.widgets.Button, + title, + row, + column, + row_span, + column_span, + padx, + pady, + command) def add_slider(self, title: str, row: int, column: int, row_span: int=1, @@ -821,26 +801,18 @@ def add_slider(self, title: str, row: int, column: int, row_span: int=1, A reference to the created slider object. """ - id = len(self.get_widgets().keys()) - new_slider = py_cui.controls.slider.SliderWidget(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - min_val, - max_val, - step, - init_val) - if self._renderer is not None: - new_slider._assign_renderer(self._renderer) - self.get_widgets()[id] = new_slider - self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}') - return new_slider + return self.add_custom_widget(py_cui.controls.slider.SliderWidget, + title, + row, + column, + row_span, + column_span, + padx, + pady, + min_val, + max_val, + step, + init_val) def forget_widget(self, widget : 'py_cui.widgets.Widget') -> None: diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 0e4357a..9bfacb6 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -10,9 +10,9 @@ import shutil from typing import Any, Union, Callable, Dict, List, Optional, TYPE_CHECKING -import py_cui.widgets as widgets -import py_cui.grid as grid -import py_cui.controls as controls +import py_cui.widgets +import py_cui.grid +import py_cui.controls if TYPE_CHECKING: import py_cui @@ -60,7 +60,7 @@ def __init__(self, num_rows: int, num_cols: int, logger: 'py_cui.debug.PyCUILogg status_bars_height = self._root.title_bar.get_height() + self._root.status_bar.get_height() self._height = self._height - status_bars_height - 2 - self._grid = grid.Grid(num_rows, num_cols, self._height, self._width, logger) + self._grid = py_cui.grid.Grid(num_rows, num_cols, self._height, self._width, logger) self._selected_widget: Optional[int] = None self._logger = logger @@ -109,7 +109,52 @@ def add_key_command(self, key: Union[int, List[int]], command: Callable[[],Any]) self._keybindings[key] = command - def add_scroll_menu(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0) -> 'py_cui.widgets.ScrollMenu': + + def add_custom_widget(self, widget_class: type, title: str, row: int, column: int, row_span: int, column_span: int, padx: int, pady: int, *args, **kwargs) -> 'py_cui.widgets.Widget': + """Function that allows for adding custom widget types to the CUI - specifically ones not included with py_cui by default + + Parameters + ---------- + widget_class : type + The class type of your custom widget. Note that it must be a subclass of the widget superclass + title : str + The title of the scroll menu + row : int + The row value, from the top down + column : int + The column value from the top down + row_span=1 : int + The number of rows to span accross + column_span=1 : int + the number of columns to span accross + padx=1 : int + number of padding characters in the x direction + pady=0 : int + number of padding characters in the y direction + + Raises + ------ + TypeError + If provided widget class is not a subclass of widget, a typeerror is raised. + """ + + if not issubclass(widget_class, py_cui.widgets.Widget): + raise TypeError(f'Widget class {widget_class} is not a subclass of the base Widget class!') + + id = len(self.get_widgets().keys()) + new_widget = widget_class(id, title, self._grid, row, column, row_span, column_span, padx, pady, self._logger, *args, **kwargs) + + self.get_widgets()[id] = new_widget + + if self._selected_widget is None: + self.set_selected_widget(id) + + self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(widget_class)}') + return new_widget + + + + def add_scroll_menu(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0) -> 'py_cui.widgets.ScrollMenu': """Function that adds a new scroll menu to the CUI grid Parameters @@ -135,22 +180,14 @@ def add_scroll_menu(self, title: str, row: int, column: int, row_span: int = 1, A reference to the created scroll menu object. """ - id = len(self.get_widgets().keys()) - new_scroll_menu = widgets.ScrollMenu(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger) - self._widgets[id] = new_scroll_menu - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}') - return new_scroll_menu + return self.add_custom_widget(py_cui.widgets.ScrollMenu, + title, + row, + column, + row_span, + column_span, + padx, + pady) def add_checkbox_menu(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0, checked_char: str='X') -> 'py_cui.widgets.CheckBoxMenu': @@ -181,23 +218,15 @@ def add_checkbox_menu(self, title: str, row: int, column: int, row_span: int=1, A reference to the created checkbox object. """ - id = len(self.get_widgets().keys()) - new_checkbox_menu = widgets.CheckBoxMenu(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - checked_char) - self._widgets[id] = new_checkbox_menu - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}') - return new_checkbox_menu + return self.add_custom_widget(py_cui.widgets.CheckBoxMenu, + title, + row, + column, + row_span, + column_span, + padx, + pady, + checked_char) def add_text_box(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, initial_text: str = '', password: bool = False) -> 'py_cui.widgets.TextBox': @@ -230,22 +259,14 @@ def add_text_box(self, title: str, row: int, column: int, row_span: int = 1, col A reference to the created textbox object. """ - id = len(self.get_widgets().keys()) - new_text_box = widgets.TextBox(id, - title, - self._grid, - row, column, - row_span, - column_span, - padx, pady, - self._logger, - initial_text, - password) - self._widgets[id] = new_text_box - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}') - return new_text_box + return self.add_custom_widget(py_cui.widgets.TextBox, + title, + row, column, + row_span, + column_span, + padx, pady, + initial_text, + password) def add_text_block(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, initial_text: str = '') -> 'py_cui.widgets.ScrollTextBlock': @@ -276,23 +297,15 @@ def add_text_block(self, title: str, row: int, column: int, row_span: int = 1, c A reference to the created textblock object. """ - id = len(self.get_widgets().keys()) - new_text_block = widgets.ScrollTextBlock(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - initial_text) - self._widgets[id] = new_text_block - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}') - return new_text_block + return self.add_custom_widget(py_cui.widgets.ScrollTextBlock, + title, + row, + column, + row_span, + column_span, + padx, + pady, + initial_text) def add_label(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0) -> 'py_cui.widgets.Label': @@ -321,20 +334,14 @@ def add_label(self, title: str, row: int, column: int, row_span: int = 1, column A reference to the created label object. """ - id = len(self.get_widgets().keys()) - new_label = widgets.Label(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger) - self._widgets[id] = new_label - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') - return new_label + return self.add_custom_widget(py_cui.widgets.Label, + title, + row, + column, + row_span, + column_span, + padx, + pady) def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, center: bool=True) -> 'py_cui.widgets.BlockLabel': @@ -365,24 +372,18 @@ def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, A reference to the created block label object. """ - id = len(self.get_widgets().keys()) - new_label = widgets.BlockLabel(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - center, - self._logger) - self._widgets[id] = new_label - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') - return new_label - - - def add_button(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, command: Optional[Callable[[],Any]]=None) -> 'py_cui.widgets.Button': + return self.add_custom_widget(py_cui.widgets.BlockLabel, + title, + row, + column, + row_span, + column_span, + padx, + pady, + center) + + + def add_button(self, title: str, row: int, column: int, row_span: int = 1, column_span: int = 1, padx: int = 1, pady: int = 0, command: Callable[[],Any]=None) -> 'py_cui.widgets.Button': """Function that adds a new button to the CUI grid Parameters @@ -410,30 +411,20 @@ def add_button(self, title: str, row: int, column: int, row_span: int = 1, colum A reference to the created button object. """ - id = len(self.get_widgets().keys()) - new_button = widgets.Button(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - command) - self._widgets[id] = new_button - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') - return new_button + self.add_custom_widget(py_cui.widgets.Button, + title, + row, + column, + row_span, + column_span, + padx, + pady, + command) def add_slider(self, title: str, row: int, column: int, row_span: int=1, column_span: int=1, padx: int=1, pady: int=0, min_val: int=0, max_val: int=100, step: int=1, init_val: int=0) -> 'py_cui.controls.slider.SliderWidget': - - """Function that adds a new label to the CUI grid Parameters @@ -461,28 +452,21 @@ def add_slider(self, title: str, row: int, column: int, row_span: int=1, init_val = 0 int initial value of the slider - Returns ------- new_slider : Slider A reference to the created slider object. """ - id = len(self._widgets.keys()) - new_slider = controls.slider.SliderWidget(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - min_val, - max_val, - step, - init_val) - self._widgets[id] = new_slider - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}') - return new_slider + return self.add_custom_widget(py_cui.controls.slider.SliderWidget, + title, + row, + column, + row_span, + column_span, + padx, + pady, + min_val, + max_val, + step, + init_val) \ No newline at end of file diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 4327fd0..8300f6f 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -27,6 +27,7 @@ import py_cui.ui import py_cui.colors import py_cui.errors +import py_cui.debug from typing import Union, Callable, List, Dict, Tuple, Any, Optional @@ -411,7 +412,7 @@ class BlockLabel(Widget): Decides whether or not label should be centered """ - def __init__(self, id, title: str, grid: 'py_cui.grid.Grid', row: int, column: int, row_span: int, column_span: int, padx: int, pady: int, center, logger): + def __init__(self, id, title: str, grid: 'py_cui.grid.Grid', row: int, column: int, row_span: int, column_span: int, padx: int, pady: int, logger: py_cui.debug.PyCUILogger, center: bool): """Initializer for blocklabel widget """ From 4717c26b73db6d67d9c1f353ac0d4ad64b4c026c Mon Sep 17 00:00:00 2001 From: PabloLec Date: Wed, 5 Jan 2022 11:18:21 +0100 Subject: [PATCH 20/30] Switch from autopep8 to yapf --- .pep8 | 5 - py_cui/__init__.py | 703 ++++++++++++++++++++----------------------- requirements_dev.txt | 1 + setup.cfg | 4 + 4 files changed, 329 insertions(+), 384 deletions(-) delete mode 100644 .pep8 create mode 100644 setup.cfg diff --git a/.pep8 b/.pep8 deleted file mode 100644 index 2fc42cc..0000000 --- a/.pep8 +++ /dev/null @@ -1,5 +0,0 @@ -[pycodestyle] -max_line_length = 88 -in-place = true -recursive = true -aggressive = 3 diff --git a/py_cui/__init__.py b/py_cui/__init__.py index e92d17e..92e9e84 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -13,10 +13,9 @@ import os import time import copy -import shutil # We use shutil for getting the terminal dimensions -import threading # Threading is used for loading icon popups -import logging # Use logging library for debug purposes - +import shutil # We use shutil for getting the terminal dimensions +import threading # Threading is used for loading icon popups +import logging # Use logging library for debug purposes # py_cui uses the curses library. On windows this does not exist, but # there is a open source windows-curses module that adds curses support @@ -72,7 +71,7 @@ def fit_text(width: int, text: str, center: bool = False) -> str: if center: left_spaces = int(total_num_spaces / 2) right_spaces = int(total_num_spaces / 2) - if(total_num_spaces % 2 == 1): + if (total_num_spaces % 2 == 1): right_spaces = right_spaces + 1 return ' ' * left_spaces + text + ' ' * right_spaces else: @@ -107,13 +106,12 @@ class PyCUI: Dimensions for an alternative simulated terminal (used for testing) """ - def __init__( - self, - num_rows: int, - num_cols: int, - auto_focus_buttons: bool = True, - exit_key=py_cui.keys.KEY_Q_LOWER, - simulated_terminal: List[int] = None): + def __init__(self, + num_rows: int, + num_cols: int, + auto_focus_buttons: bool = True, + exit_key=py_cui.keys.KEY_Q_LOWER, + simulated_terminal: List[int] = None): """Initializer for PyCUI class """ @@ -136,8 +134,10 @@ def __init__( width = self._simulated_terminal[1] # Add status and title bar - self.title_bar = py_cui.statusbar.StatusBar( - self._title, BLACK_ON_WHITE, root=self, is_title_bar=True) + self.title_bar = py_cui.statusbar.StatusBar(self._title, + BLACK_ON_WHITE, + root=self, + is_title_bar=True) exit_key_char = py_cui.keys.get_char_from_ascii(exit_key) if exit_key_char: @@ -148,26 +148,22 @@ def __init__( self._init_status_bar_text = 'Press arrow Keys to move between widgets. ' \ 'Enter to enter focus mode.' \ - self.status_bar = py_cui.statusbar.StatusBar(self._init_status_bar_text, - BLACK_ON_WHITE, root=self) + self.status_bar = py_cui.statusbar.StatusBar( + self._init_status_bar_text, BLACK_ON_WHITE, root=self) # Init terminal height width. Subtract 4 from height # for title/status bar and padding self._height = height self._width = width - self._height = self._height - self.title_bar.get_height() - self.status_bar.get_height() - 2 + self._height = self._height - self.title_bar.get_height( + ) - self.status_bar.get_height() - 2 # Logging object initialization for py_cui - self._logger = py_cui.debug._initialize_logger(self, - name='py_cui') + self._logger = py_cui.debug._initialize_logger(self, name='py_cui') # Initialize grid, renderer, and widget dict - self._grid = py_cui.grid.Grid( - num_rows, - num_cols, - self._height, - self._width, - self._logger) + self._grid = py_cui.grid.Grid(num_rows, num_cols, self._height, + self._width, self._logger) self._stdscr: Any = None self._refresh_timeout = -1 self._border_characters: Optional[Dict[str, str]] = None @@ -219,10 +215,9 @@ def set_on_draw_update_func(self, update_function: Callable[[], Any]): self._on_draw_update_func = update_function - def set_widget_cycle_key( - self, - forward_cycle_key: int = None, - reverse_cycle_key: int = None) -> None: + def set_widget_cycle_key(self, + forward_cycle_key: int = None, + reverse_cycle_key: int = None) -> None: """Assigns a key for automatically cycling through widgets in both focus and overview modes Parameters @@ -239,11 +234,10 @@ def set_widget_cycle_key( def set_toggle_live_debug_key(self, toggle_debug_key: int) -> None: self._toggle_live_debug_key = toggle_debug_key - def enable_logging( - self, - log_file_path: str = 'py_cui.log', - logging_level=logging.DEBUG, - live_debug_key: int = py_cui.keys.KEY_CTRL_D) -> None: + def enable_logging(self, + log_file_path: str = 'py_cui.log', + logging_level=logging.DEBUG, + live_debug_key: int = py_cui.keys.KEY_CTRL_D) -> None: """Function enables logging for py_cui library Parameters @@ -255,16 +249,16 @@ def enable_logging( """ try: - py_cui.debug._enable_logging( - self._logger, - filename=log_file_path, - logging_level=logging_level) + py_cui.debug._enable_logging(self._logger, + filename=log_file_path, + logging_level=logging_level) self._logger.info('Initialized logger') self._toggle_live_debug_key = live_debug_key except PermissionError as e: print(f'Failed to initialize logger: {str(e)}') - def apply_widget_set(self, new_widget_set: py_cui.widget_set.WidgetSet) -> None: + def apply_widget_set(self, + new_widget_set: py_cui.widget_set.WidgetSet) -> None: """Function that replaces all widgets in a py_cui with those of a different widget set Parameters @@ -289,12 +283,11 @@ def apply_widget_set(self, new_widget_set: py_cui.widget_set.WidgetSet) -> None: self._initialize_widget_renderer() self._selected_widget = new_widget_set._selected_widget else: - raise TypeError('Argument must be of type py_cui.widget_set.WidgetSet') + raise TypeError( + 'Argument must be of type py_cui.widget_set.WidgetSet') - def create_new_widget_set( - self, - num_rows: int, - num_cols: int) -> 'py_cui.widget_set.WidgetSet': + def create_new_widget_set(self, num_rows: int, + num_cols: int) -> 'py_cui.widget_set.WidgetSet': """Function that is used to create additional widget sets Use this function instead of directly creating widget set object instances, to allow @@ -314,8 +307,12 @@ def create_new_widget_set( """ # Use current logging object and simulated terminal for sub-widget sets - return py_cui.widget_set.WidgetSet(num_rows, num_cols, self._logger, root=self, - simulated_terminal=self._simulated_terminal) + return py_cui.widget_set.WidgetSet( + num_rows, + num_cols, + self._logger, + root=self, + simulated_terminal=self._simulated_terminal) # ----------------------------------------------# # Initialization functions # @@ -389,7 +386,8 @@ def _initialize_widget_renderer(self) -> None: """ if self._renderer is None: - self._renderer = py_cui.renderer.Renderer(self, self._stdscr, self._logger) + self._renderer = py_cui.renderer.Renderer(self, self._stdscr, + self._logger) for widget_id in self.get_widgets().keys(): widget = self.get_widgets()[widget_id] if widget is not None: @@ -397,12 +395,14 @@ def _initialize_widget_renderer(self) -> None: widget._assign_renderer(self._renderer) except py_cui.errors.PyCUIError: self._logger.debug( - f'Renderer already assigned for widget {self.get_widgets()[widget_id]}') + f'Renderer already assigned for widget {self.get_widgets()[widget_id]}' + ) try: if self._popup is not None: self._popup._assign_renderer(self._renderer) if self._logger is not None: - self._logger._live_debug_element._assign_renderer(self._renderer) + self._logger._live_debug_element._assign_renderer( + self._renderer) except py_cui.errors.PyCUIError: self._logger.debug( 'Renderer already assigned to popup or live-debug elements') @@ -411,20 +411,18 @@ def toggle_unicode_borders(self) -> None: """Function for toggling unicode based border rendering """ - if self._border_characters is None or self._border_characters['UP_LEFT'] == '+': - self.set_widget_border_characters( - '\u256d', '\u256e', '\u2570', '\u256f', '\u2500', '\u2502') + if self._border_characters is None or self._border_characters[ + 'UP_LEFT'] == '+': + self.set_widget_border_characters('\u256d', '\u256e', '\u2570', + '\u256f', '\u2500', '\u2502') else: self.set_widget_border_characters('+', '+', '+', '+', '-', '|') - def set_widget_border_characters( - self, - upper_left_corner: str, - upper_right_corner: str, - lower_left_corner: str, - lower_right_corner: str, - horizontal: str, - vertical: str) -> None: + def set_widget_border_characters(self, upper_left_corner: str, + upper_right_corner: str, + lower_left_corner: str, + lower_right_corner: str, horizontal: str, + vertical: str) -> None: """Function that can be used to set arbitrary border characters for drawing widget borders by renderer. Parameters @@ -451,7 +449,8 @@ def set_widget_border_characters( 'HORIZONTAL': horizontal, 'VERTICAL': vertical } - self._logger.debug(f'Set border_characters to {self._border_characters}') + self._logger.debug( + f'Set border_characters to {self._border_characters}') def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']]: """Function that gets current set of widgets @@ -467,15 +466,14 @@ def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']]: # Widget add functions. Each of these adds a particular type of widget # to the grid in a specified location. - def add_scroll_menu( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0) -> 'py_cui.widgets.ScrollMenu': + def add_scroll_menu(self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0) -> 'py_cui.widgets.ScrollMenu': """Function that adds a new scroll menu to the CUI grid Parameters @@ -502,15 +500,9 @@ def add_scroll_menu( """ id = len(self.get_widgets().keys()) - new_scroll_menu = py_cui.widgets.ScrollMenu(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, + new_scroll_menu = py_cui.widgets.ScrollMenu(id, title, self._grid, row, + column, row_span, + column_span, padx, pady, self._logger) if self._renderer is not None: new_scroll_menu._assign_renderer(self._renderer) @@ -518,7 +510,8 @@ def add_scroll_menu( if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}') + f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}' + ) return new_scroll_menu def add_checkbox_menu( @@ -559,37 +552,29 @@ def add_checkbox_menu( """ id = len(self.get_widgets().keys()) - new_checkbox_menu = py_cui.widgets.CheckBoxMenu(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - checked_char) + new_checkbox_menu = py_cui.widgets.CheckBoxMenu( + id, title, self._grid, row, column, row_span, column_span, padx, + pady, self._logger, checked_char) if self._renderer is not None: new_checkbox_menu._assign_renderer(self._renderer) self.get_widgets()[id] = new_checkbox_menu if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}') + f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}' + ) return new_checkbox_menu - def add_text_box( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - initial_text: str = '', - password: bool = False) -> 'py_cui.widgets.TextBox': + def add_text_box(self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + initial_text: str = '', + password: bool = False) -> 'py_cui.widgets.TextBox': """Function that adds a new text box to the CUI grid Parameters @@ -620,23 +605,18 @@ def add_text_box( """ id = len(self.get_widgets().keys()) - new_text_box = py_cui.widgets.TextBox(id, - title, - self._grid, - row, column, - row_span, - column_span, - padx, pady, - self._logger, - initial_text, - password) + new_text_box = py_cui.widgets.TextBox(id, title, self._grid, row, + column, row_span, column_span, + padx, pady, self._logger, + initial_text, password) if self._renderer is not None: new_text_box._assign_renderer(self._renderer) self.get_widgets()[id] = new_text_box if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}') + f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}' + ) return new_text_box def add_text_block( @@ -677,35 +657,27 @@ def add_text_block( """ id = len(self.get_widgets().keys()) - new_text_block = py_cui.widgets.ScrollTextBlock(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - initial_text) + new_text_block = py_cui.widgets.ScrollTextBlock( + id, title, self._grid, row, column, row_span, column_span, padx, + pady, self._logger, initial_text) if self._renderer is not None: new_text_block._assign_renderer(self._renderer) self.get_widgets()[id] = new_text_block if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}') + f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}' + ) return new_text_block - def add_label( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0) -> 'py_cui.widgets.Label': + def add_label(self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0) -> 'py_cui.widgets.Label': """Function that adds a new label to the CUI grid Parameters @@ -732,15 +704,8 @@ def add_label( """ id = len(self.get_widgets().keys()) - new_label = py_cui.widgets.Label(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, + new_label = py_cui.widgets.Label(id, title, self._grid, row, column, + row_span, column_span, padx, pady, self._logger) if self._renderer is not None: new_label._assign_renderer(self._renderer) @@ -749,16 +714,15 @@ def add_label( f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') return new_label - def add_block_label( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - center: bool = True) -> 'py_cui.widgets.BlockLabel': + def add_block_label(self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + center: bool = True) -> 'py_cui.widgets.BlockLabel': """Function that adds a new block label to the CUI grid Parameters @@ -787,17 +751,9 @@ def add_block_label( """ id = len(self.get_widgets().keys()) - new_label = py_cui.widgets.BlockLabel(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - center, - self._logger) + new_label = py_cui.widgets.BlockLabel(id, title, self._grid, row, + column, row_span, column_span, + padx, pady, center, self._logger) if self._renderer is not None: new_label._assign_renderer(self._renderer) self.get_widgets()[id] = new_label @@ -806,16 +762,15 @@ def add_block_label( return new_label def add_button( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - command: Callable[[], - Any] = None) -> 'py_cui.widgets.Button': + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + command: Callable[[], Any] = None) -> 'py_cui.widgets.Button': """Function that adds a new button to the CUI grid Parameters @@ -844,39 +799,31 @@ def add_button( """ id = len(self.get_widgets().keys()) - new_button = py_cui.widgets.Button(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - command) + new_button = py_cui.widgets.Button(id, title, self._grid, row, column, + row_span, column_span, padx, pady, + self._logger, command) if self._renderer is not None: new_button._assign_renderer(self._renderer) self.get_widgets()[id] = new_button if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') + f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}' + ) return new_button - def add_slider( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - min_val: int = 0, - max_val: int = 100, - step: int = 1, - init_val: int = 0) -> 'py_cui.controls.slider.SliderWidget': + def add_slider(self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + min_val: int = 0, + max_val: int = 100, + step: int = 1, + init_val: int = 0) -> 'py_cui.controls.slider.SliderWidget': """Function that adds a new label to the CUI grid Parameters @@ -911,25 +858,15 @@ def add_slider( """ id = len(self.get_widgets().keys()) - new_slider = py_cui.controls.slider.SliderWidget(id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger, - min_val, - max_val, - step, - init_val) + new_slider = py_cui.controls.slider.SliderWidget( + id, title, self._grid, row, column, row_span, column_span, padx, + pady, self._logger, min_val, max_val, step, init_val) if self._renderer is not None: new_slider._assign_renderer(self._renderer) self.get_widgets()[id] = new_slider self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}') + f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}' + ) return new_slider def forget_widget(self, widget: 'py_cui.widgets.Widget') -> None: @@ -949,17 +886,17 @@ def forget_widget(self, widget: 'py_cui.widgets.Widget') -> None: """ if not isinstance(widget, py_cui.widgets.Widget): - raise TypeError('Argument widget must by of type py_cui.widgets.Widget!') + raise TypeError( + 'Argument widget must by of type py_cui.widgets.Widget!') elif widget.get_id() not in self.get_widgets().keys(): raise KeyError( - f'Widget with id {widget.get_id()} has already been removed from the UI!') + f'Widget with id {widget.get_id()} has already been removed from the UI!' + ) else: self.get_widgets()[widget.get_id()] = None - def get_element_at_position( - self, - x: int, - y: int) -> Optional['py_cui.ui.UIElement']: + def get_element_at_position(self, x: int, + y: int) -> Optional['py_cui.ui.UIElement']: """Returns containing widget for character position Parameters @@ -986,8 +923,7 @@ def get_element_at_position( return widget return None - def _get_horizontal_neighbors(self, - widget: 'py_cui.widgets.Widget', + def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]]: """Gets all horizontal (left, right) neighbor widgets @@ -1033,12 +969,12 @@ def _get_horizontal_neighbors(self, id_list.reverse() self._logger.debug(f'Neighbors with ids {id_list} for cell \ - {row_start},{col_start} span {row_span},{col_span}') + {row_start},{col_start} span {row_span},{col_span}' + ) return id_list - def _get_vertical_neighbors(self, - widget: 'py_cui.widgets.Widget', + def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]]: """Gets all vertical (up, down) neighbor widgets @@ -1083,7 +1019,8 @@ def _get_vertical_neighbors(self, id_list.reverse() self._logger.debug(f'Neighbors with ids {id_list} for cell \ - {row_start},{col_start} span {row_span},{col_span}') + {row_start},{col_start} span {row_span},{col_span}' + ) return id_list @@ -1113,10 +1050,16 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: # Find all the widgets in the given row or column neighbors: Optional[List[int]] = [] if start_widget is not None: - if direction in [py_cui.keys.KEY_DOWN_ARROW, py_cui.keys.KEY_UP_ARROW]: - neighbors = self._get_vertical_neighbors(start_widget, direction) - elif direction in [py_cui.keys.KEY_RIGHT_ARROW, py_cui.keys.KEY_LEFT_ARROW]: - neighbors = self._get_horizontal_neighbors(start_widget, direction) + if direction in [ + py_cui.keys.KEY_DOWN_ARROW, py_cui.keys.KEY_UP_ARROW + ]: + neighbors = self._get_vertical_neighbors( + start_widget, direction) + elif direction in [ + py_cui.keys.KEY_RIGHT_ARROW, py_cui.keys.KEY_LEFT_ARROW + ]: + neighbors = self._get_horizontal_neighbors( + start_widget, direction) if neighbors is None or len(neighbors) == 0: return None @@ -1133,7 +1076,8 @@ def get_selected_widget(self) -> Optional['py_cui.widgets.Widget']: Reference to currently selected widget object """ - if self._selected_widget is not None and self._selected_widget in self.get_widgets().keys(): + if self._selected_widget is not None and self._selected_widget in self.get_widgets( + ).keys(): return self.get_widgets()[self._selected_widget] else: self._logger.warn('Selected widget ID is None or invalid') @@ -1153,7 +1097,8 @@ def set_selected_widget(self, widget_id: int) -> None: self._selected_widget = widget_id else: self._logger.warn( - f'Widget w/ ID {widget_id} does not exist among current widgets.') + f'Widget w/ ID {widget_id} does not exist among current widgets.' + ) def lose_focus(self) -> None: """Function that forces py_cui out of focus mode. @@ -1171,7 +1116,8 @@ def lose_focus(self) -> None: else: self._logger.info('lose_focus: Not currently in focus mode') - def move_focus(self, widget: 'py_cui.widgets.Widget', + def move_focus(self, + widget: 'py_cui.widgets.Widget', auto_press_buttons: bool = True) -> None: """Moves focus mode to different widget @@ -1192,9 +1138,11 @@ def move_focus(self, widget: 'py_cui.widgets.Widget', widget.command() self._logger.debug( - f'Moved focus to button {widget.get_title()} - ran autofocus command') + f'Moved focus to button {widget.get_title()} - ran autofocus command' + ) - elif self._auto_focus_buttons and isinstance(widget, py_cui.widgets.Button): + elif self._auto_focus_buttons and isinstance(widget, + py_cui.widgets.Button): self.status_bar.set_text(self._init_status_bar_text) else: widget.set_selected(True) @@ -1212,36 +1160,33 @@ def _cycle_widgets(self, reverse: bool = False) -> None: Default false. If true, cycle widgets in reverse order. """ - num_widgets = len(self.get_widgets().keys()) + num_widgets: int = len(self.get_widgets()) current_widget_num: Optional[int] = self._selected_widget - if current_widget_num is not None: - if not reverse: - next_widget_num = current_widget_num + 1 - if self.get_widgets()[next_widget_num] is None: - if next_widget_num == num_widgets: - next_widget_num = 0 - next_widget_num = next_widget_num + 1 - cycle_key = self._forward_cycle_key - else: - next_widget_num = current_widget_num - 1 - if self.get_widgets()[next_widget_num] is None: - if next_widget_num < 0: - next_widget_num = num_widgets - 1 - next_widget_num = next_widget_num + 1 - cycle_key = self._reverse_cycle_key - - current_widget_id: int = current_widget_num - next_widget_id: int = next_widget_num - current_widget = self.get_widgets()[current_widget_id] - next_widget = self.get_widgets()[next_widget_id] - if current_widget and next_widget is not None: # pls check again - if self._in_focused_mode and cycle_key in current_widget._key_commands.keys(): - # In the event that we are focusing on a widget with that key defined, - # we do not cycle. - pass - else: - self.move_focus(next_widget, auto_press_buttons=False) + if current_widget_num is None: + return + + if reverse: + next_widget_num = current_widget_num - 1 + if next_widget_num < 0: + next_widget_num = num_widgets - 1 + cycle_key = self._reverse_cycle_key + else: + next_widget_num = current_widget_num + 1 + if next_widget_num >= num_widgets: + next_widget_num = 0 + cycle_key = self._forward_cycle_key + + current_widget = self.get_widgets().get(current_widget_num) + next_widget = self.get_widgets().get(next_widget_num) + + if current_widget is not None and next_widget is not None: + if self._in_focused_mode and cycle_key in current_widget._key_commands.keys( + ): + # In the event that we are focusing on a widget with that key defined, we + # do not cycle. + return + self.move_focus(next_widget, auto_press_buttons=False) def add_key_command(self, key: Union[int, List[int]], command: Callable[[], Any]) -> None: @@ -1263,11 +1208,10 @@ def add_key_command(self, key: Union[int, List[int]], # Popup functions. Used to display messages, warnings, and errors to the user. - def show_message_popup( - self, - title: str, - text: str, - color: int = WHITE_ON_BLACK) -> None: + def show_message_popup(self, + title: str, + text: str, + color: int = WHITE_ON_BLACK) -> None: """Shows a message popup Parameters @@ -1280,9 +1224,10 @@ def show_message_popup( Popup color with format FOREGOUND_ON_BACKGROUND. See colors module. Default: WHITE_ON_BLACK. """ - self._popup = py_cui.popups.MessagePopup( - self, title, text, color, self._renderer, self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') + self._popup = py_cui.popups.MessagePopup(self, title, text, color, + self._renderer, self._logger) + self._logger.debug( + f'Opened {str(type(self._popup))} popup with title {title}') def show_warning_popup(self, title: str, text: str) -> None: """Shows a warning popup @@ -1324,18 +1269,17 @@ def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]): """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.YesNoPopup( - self, - title + '- (y/n)', - 'Yes - (y), No - (n)', - color, - command, - self._renderer, - self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') + self._popup = py_cui.popups.YesNoPopup(self, title + '- (y/n)', + 'Yes - (y), No - (n)', color, + command, self._renderer, + self._logger) + self._logger.debug( + f'Opened {str(type(self._popup))} popup with title {title}') - def show_text_box_popup(self, title: str, command: Callable[[ - str], Any], password: bool = False): + def show_text_box_popup(self, + title: str, + command: Callable[[str], Any], + password: bool = False): """Shows a textbox popup. The 'command' parameter must be a function with a single string parameter @@ -1351,12 +1295,17 @@ def show_text_box_popup(self, title: str, command: Callable[[ """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.TextBoxPopup( - self, title, color, command, self._renderer, password, self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') + self._popup = py_cui.popups.TextBoxPopup(self, title, color, command, + self._renderer, password, + self._logger) + self._logger.debug( + f'Opened {str(type(self._popup))} popup with title {title}') - def show_menu_popup(self, title: str, menu_items: List[str], command: Callable[[ - str], Any], run_command_if_none: bool = False): + def show_menu_popup(self, + title: str, + menu_items: List[str], + command: Callable[[str], Any], + run_command_if_none: bool = False): """Shows a menu popup. The 'command' parameter must be a function with a single string parameter @@ -1374,23 +1323,17 @@ def show_menu_popup(self, title: str, menu_items: List[str], command: Callable[[ """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.MenuPopup( - self, - menu_items, - title, - color, - command, - self._renderer, - self._logger, - run_command_if_none) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_loading_icon_popup( - self, - title: str, - message: str, - callback: Callable[[], - Any] = None): + self._popup = py_cui.popups.MenuPopup(self, menu_items, title, color, + command, self._renderer, + self._logger, + run_command_if_none) + self._logger.debug( + f'Opened {str(type(self._popup))} popup with title {title}') + + def show_loading_icon_popup(self, + title: str, + message: str, + callback: Callable[[], Any] = None): """Shows a loading icon popup Parameters @@ -1405,20 +1348,21 @@ def show_loading_icon_popup( if callback is not None: self._post_loading_callback = callback - self._logger.debug(f'Post loading callback funciton set to {str(callback)}') + self._logger.debug( + f'Post loading callback funciton set to {str(callback)}') color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingIconPopup( - self, title, message, color, self._renderer, self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_loading_bar_popup( - self, - title: str, - num_items: List[int], - callback: Callable[[], - Any] = None) -> None: + self._popup = py_cui.popups.LoadingIconPopup(self, title, message, + color, self._renderer, + self._logger) + self._logger.debug( + f'Opened {str(type(self._popup))} popup with title {title}') + + def show_loading_bar_popup(self, + title: str, + num_items: List[int], + callback: Callable[[], Any] = None) -> None: """Shows loading bar popup. Use 'increment_loading_bar' to show progress @@ -1435,22 +1379,23 @@ def show_loading_bar_popup( if callback is not None: self._post_loading_callback = callback - self._logger.debug(f'Post loading callback funciton set to {str(callback)}') + self._logger.debug( + f'Post loading callback funciton set to {str(callback)}') color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingBarPopup( - self, title, num_items, color, self._renderer, self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - - def show_form_popup( - self, - title: str, - fields: List[str], - passwd_fields: List[str] = [], - required: List[str] = [], - callback: Callable[[], - Any] = None) -> None: + self._popup = py_cui.popups.LoadingBarPopup(self, title, num_items, + color, self._renderer, + self._logger) + self._logger.debug( + f'Opened {str(type(self._popup))} popup with title {title}') + + def show_form_popup(self, + title: str, + fields: List[str], + passwd_fields: List[str] = [], + required: List[str] = [], + callback: Callable[[], Any] = None) -> None: """Shows form popup. Used for inputting several fields worth of values @@ -1469,30 +1414,24 @@ def show_form_popup( If not none, fired after loading is completed. Must be a no-arg function """ - self._popup = py_cui.dialogs.form.FormPopup(self, - fields, - passwd_fields, - required, - {}, - title, - py_cui.WHITE_ON_BLACK, - self._renderer, - self._logger) + self._popup = py_cui.dialogs.form.FormPopup( + self, fields, passwd_fields, required, {}, title, + py_cui.WHITE_ON_BLACK, self._renderer, self._logger) if callback is not None: self._popup.set_on_submit_action(callback) - self._logger.debug(f'Form enter callback funciton set to {str(callback)}') - - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') + self._logger.debug( + f'Form enter callback funciton set to {str(callback)}') - def show_filedialog_popup( - self, - popup_type: str = 'openfile', - initial_dir: str = '.', - callback: Callable[[], - Any] = None, - ascii_icons: bool = True, - limit_extensions: List[str] = []) -> None: + self._logger.debug( + f'Opened {str(type(self._popup))} popup with title {title}') + + def show_filedialog_popup(self, + popup_type: str = 'openfile', + initial_dir: str = '.', + callback: Callable[[], Any] = None, + ascii_icons: bool = True, + limit_extensions: List[str] = []) -> None: """Shows form popup. Used for inputting several fields worth of values @@ -1511,15 +1450,10 @@ def show_filedialog_popup( Only show files with extensions in this list if not empty. Default, [] """ - self._popup = py_cui.dialogs.filedialog.FileDialogPopup(self, - callback, - initial_dir, - popup_type, - ascii_icons, - limit_extensions, - py_cui.WHITE_ON_BLACK, - self._renderer, - self._logger) + self._popup = py_cui.dialogs.filedialog.FileDialogPopup( + self, callback, initial_dir, popup_type, ascii_icons, + limit_extensions, py_cui.WHITE_ON_BLACK, self._renderer, + self._logger) self._logger.debug( f'Opened {str(type(self._popup))} popup with type {popup_type}') @@ -1565,15 +1499,18 @@ def _refresh_height_width(self) -> None: height = self._simulated_terminal[0] width = self._simulated_terminal[1] - height = height - self.title_bar.get_height() - self.status_bar.get_height() - 2 + height = height - self.title_bar.get_height( + ) - self.status_bar.get_height() - 2 - self._logger.debug(f'Resizing CUI to new dimensions {height} by {width}') + self._logger.debug( + f'Resizing CUI to new dimensions {height} by {width}') self._height = height self._width = width self._grid.update_grid_height_width(self._height, self._width) for widget_id in self.get_widgets().keys(): - widget = self.get_widgets()[widget_id] # using temp variable, for mypy + widget = self.get_widgets()[ + widget_id] # using temp variable, for mypy if widget is not None: widget.update_height_width() if self._popup is not None: @@ -1630,7 +1567,8 @@ def _draw_status_bars(self, stdscr, height: int, width: int) -> None: if self.status_bar is not None and self.status_bar.get_height() > 0: stdscr.attron(curses.color_pair(self.status_bar.get_color())) - stdscr.addstr(height + 3, 0, fit_text(width, self.status_bar.get_text())) + stdscr.addstr(height + 3, 0, + fit_text(width, self.status_bar.get_text())) stdscr.attroff(curses.color_pair(self.status_bar.get_color())) if self.title_bar is not None and self.title_bar.get_height() > 0: @@ -1681,8 +1619,7 @@ def _handle_key_presses(self, key_pressed: int) -> None: if key_pressed == self._toggle_live_debug_key: self._logger.toggle_live_debug() - # If we are in live debug mode, we only handle keypresses for the live - # debug UI element + # If we are in live debug mode, we only handle keypresses for the live debug UI element if self._logger is not None and self._logger.is_live_debug_enabled(): self._logger._live_debug_element._handle_key_press(key_pressed) @@ -1694,24 +1631,28 @@ def _handle_key_presses(self, key_pressed: int) -> None: self._in_focused_mode = False selected_widget.set_selected(False) self._logger.debug( - f'Exiting focus mode on widget {selected_widget.get_title()}') + f'Exiting focus mode on widget {selected_widget.get_title()}' + ) else: # widget handles remaining py_cui.keys self._logger.debug( - f'Widget {selected_widget.get_title()} handling {key_pressed} key') + f'Widget {selected_widget.get_title()} handling {key_pressed} key' + ) 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(): + if key_pressed == py_cui.keys.KEY_ENTER and self._selected_widget is not None and selected_widget.is_selectable( + ): self.move_focus(selected_widget) for key in self._keybindings.keys(): if key_pressed == key: command = self._keybindings[key] self._logger.info( - f'Detected binding for key {key_pressed}, running command {command.__name__}') + f'Detected binding for key {key_pressed}, running command {command.__name__}' + ) command() # If not in focus mode, use the arrow py_cui.keys to move around the @@ -1800,27 +1741,31 @@ def _draw(self, stdscr) -> None: id, x, y, _, mouse_event = curses.getmouse() except curses.error as e: valid_mouse_event = False - self._logger.error(f'Failed to handle mouse event: {str(e)}') + self._logger.error( + f'Failed to handle mouse event: {str(e)}') if valid_mouse_event: in_element = self.get_element_at_position(x, y) - # In first case, we click inside already selected widget, pass - # click for processing + # In first case, we click inside already selected widget, pass click + # for processing if in_element is not None: self._logger.info( - f'handling mouse press for elem: {in_element.get_title()}') + f'handling mouse press for elem: {in_element.get_title()}' + ) in_element._handle_mouse_press(x, y, mouse_event) # Otherwise, if not a popup, select the clicked on widget - elif in_element is not None and not isinstance(in_element, py_cui.popups.Popup): + elif in_element is not None and not isinstance( + in_element, py_cui.popups.Popup): self.move_focus(in_element) in_element._handle_mouse_press(x, y, mouse_event) # If we have a post_loading_callback, fire it here if self._post_loading_callback is not None and not self._loading: self._logger.debug( - f'Firing post-loading callback function {self._post_loading_callback.__name__}') + f'Firing post-loading callback function {self._post_loading_callback.__name__}' + ) self._post_loading_callback() self._post_loading_callback = None @@ -1834,8 +1779,7 @@ def _draw(self, stdscr) -> None: self._handle_key_presses(key_pressed) try: - # Draw status/title bar, and all widgets. Selected widget will be - # bolded. + # Draw status/title bar, and all widgets. Selected widget will be bolded. self._draw_status_bars(stdscr, self._height, self._width) self._draw_widgets() # draw the popup if required @@ -1860,8 +1804,8 @@ def _draw(self, stdscr) -> None: 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. + # 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 @@ -1877,7 +1821,8 @@ def _draw(self, stdscr) -> None: stdscr.refresh() curses.endwin() if self._on_stop is not None: - self._logger.debug(f'Firing onstop function {self._on_stop.__name__}') + self._logger.debug( + f'Firing onstop function {self._on_stop.__name__}') self._on_stop() def __format__(self, fmt): diff --git a/requirements_dev.txt b/requirements_dev.txt index ecfb67a..10ac091 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -4,3 +4,4 @@ wheel npdoc2md mkdocs windows-curses ; platform_system=="Windows" +yapf diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..53d642b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[style] +based_on_style = pep8 +column_limit = 95 +use_tabs = true From f6f5f55101fe5dd2558cff75f2a796b3b0f27876 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Wed, 5 Jan 2022 11:35:11 +0100 Subject: [PATCH 21/30] Switch from yapf to black --- py_cui/__init__.py | 883 +++++++++++++++++++++++-------------------- requirements_dev.txt | 2 +- setup.cfg | 4 - 3 files changed, 481 insertions(+), 408 deletions(-) delete mode 100644 setup.cfg diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 92e9e84..0d43680 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -17,6 +17,7 @@ import threading # Threading is used for loading icon popups import logging # Use logging library for debug purposes + # py_cui uses the curses library. On windows this does not exist, but # there is a open source windows-curses module that adds curses support # for python on windows @@ -38,7 +39,7 @@ from py_cui.colors import * # Version number -__version__ = '0.1.5' +__version__ = "0.1.5" def fit_text(width: int, text: str, center: bool = False) -> str: @@ -63,19 +64,19 @@ def fit_text(width: int, text: str, center: bool = False) -> str: """ if width < 5: - return '.' * width + return "." * width if len(text) >= width: - return text[:width - 5] + '...' + return text[: width - 5] + "..." else: - total_num_spaces = (width - len(text) - 1) + total_num_spaces = width - len(text) - 1 if center: left_spaces = int(total_num_spaces / 2) right_spaces = int(total_num_spaces / 2) - if (total_num_spaces % 2 == 1): + if total_num_spaces % 2 == 1: right_spaces = right_spaces + 1 - return ' ' * left_spaces + text + ' ' * right_spaces + return " " * left_spaces + text + " " * right_spaces else: - return text + ' ' * total_num_spaces + return text + " " * total_num_spaces class PyCUI: @@ -106,20 +107,21 @@ class PyCUI: Dimensions for an alternative simulated terminal (used for testing) """ - def __init__(self, - num_rows: int, - num_cols: int, - auto_focus_buttons: bool = True, - exit_key=py_cui.keys.KEY_Q_LOWER, - simulated_terminal: List[int] = None): - """Initializer for PyCUI class - """ + def __init__( + self, + num_rows: int, + num_cols: int, + auto_focus_buttons: bool = True, + exit_key=py_cui.keys.KEY_Q_LOWER, + simulated_terminal: List[int] = None, + ): + """Initializer for PyCUI class""" - self._title = 'PyCUI Window' + self._title = "PyCUI Window" # When this is not set, the escape character delay # is too long for exiting focus mode - os.environ.setdefault('ESCDELAY', '25') + os.environ.setdefault("ESCDELAY", "25") # For unit testing purposes, we want to simulate terminal # dimensions so that we don't get errors @@ -134,41 +136,45 @@ def __init__(self, width = self._simulated_terminal[1] # Add status and title bar - self.title_bar = py_cui.statusbar.StatusBar(self._title, - BLACK_ON_WHITE, - root=self, - is_title_bar=True) + self.title_bar = py_cui.statusbar.StatusBar( + self._title, BLACK_ON_WHITE, root=self, is_title_bar=True + ) exit_key_char = py_cui.keys.get_char_from_ascii(exit_key) if exit_key_char: - self._init_status_bar_text = f'Press - {exit_key_char} - to exit. Arrow ' \ - 'Keys to move between widgets. Enter to ' \ - 'enter focus mode.' + self._init_status_bar_text = ( + f"Press - {exit_key_char} - to exit. Arrow " + "Keys to move between widgets. Enter to " + "enter focus mode." + ) else: - self._init_status_bar_text = 'Press arrow Keys to move between widgets. ' \ - 'Enter to enter focus mode.' \ - + self._init_status_bar_text = ( + "Press arrow Keys to move between widgets. " "Enter to enter focus mode." + ) self.status_bar = py_cui.statusbar.StatusBar( - self._init_status_bar_text, BLACK_ON_WHITE, root=self) + self._init_status_bar_text, BLACK_ON_WHITE, root=self + ) # Init terminal height width. Subtract 4 from height # for title/status bar and padding self._height = height self._width = width - self._height = self._height - self.title_bar.get_height( - ) - self.status_bar.get_height() - 2 + self._height = ( + self._height - self.title_bar.get_height() - self.status_bar.get_height() - 2 + ) # Logging object initialization for py_cui - self._logger = py_cui.debug._initialize_logger(self, name='py_cui') + self._logger = py_cui.debug._initialize_logger(self, name="py_cui") # Initialize grid, renderer, and widget dict - self._grid = py_cui.grid.Grid(num_rows, num_cols, self._height, - self._width, self._logger) + self._grid = py_cui.grid.Grid( + num_rows, num_cols, self._height, self._width, self._logger + ) self._stdscr: Any = None self._refresh_timeout = -1 self._border_characters: Optional[Dict[str, str]] = None - self._widgets: Dict[int, Optional['py_cui.widgets.Widget']] = {} - self._renderer: Optional['py_cui.renderer.Renderer'] = None + self._widgets: Dict[int, Optional["py_cui.widgets.Widget"]] = {} + self._renderer: Optional["py_cui.renderer.Renderer"] = None # Variables for determining selected widget/focus mode self._selected_widget: Optional[int] = None @@ -215,9 +221,9 @@ def set_on_draw_update_func(self, update_function: Callable[[], Any]): self._on_draw_update_func = update_function - def set_widget_cycle_key(self, - forward_cycle_key: int = None, - reverse_cycle_key: int = None) -> None: + def set_widget_cycle_key( + self, forward_cycle_key: int = None, reverse_cycle_key: int = None + ) -> None: """Assigns a key for automatically cycling through widgets in both focus and overview modes Parameters @@ -234,10 +240,12 @@ def set_widget_cycle_key(self, def set_toggle_live_debug_key(self, toggle_debug_key: int) -> None: self._toggle_live_debug_key = toggle_debug_key - def enable_logging(self, - log_file_path: str = 'py_cui.log', - logging_level=logging.DEBUG, - live_debug_key: int = py_cui.keys.KEY_CTRL_D) -> None: + def enable_logging( + self, + log_file_path: str = "py_cui.log", + logging_level=logging.DEBUG, + live_debug_key: int = py_cui.keys.KEY_CTRL_D, + ) -> None: """Function enables logging for py_cui library Parameters @@ -249,16 +257,15 @@ def enable_logging(self, """ try: - py_cui.debug._enable_logging(self._logger, - filename=log_file_path, - logging_level=logging_level) - self._logger.info('Initialized logger') + py_cui.debug._enable_logging( + self._logger, filename=log_file_path, logging_level=logging_level + ) + self._logger.info("Initialized logger") self._toggle_live_debug_key = live_debug_key except PermissionError as e: - print(f'Failed to initialize logger: {str(e)}') + print(f"Failed to initialize logger: {str(e)}") - def apply_widget_set(self, - new_widget_set: py_cui.widget_set.WidgetSet) -> None: + def apply_widget_set(self, new_widget_set: py_cui.widget_set.WidgetSet) -> None: """Function that replaces all widgets in a py_cui with those of a different widget set Parameters @@ -283,11 +290,11 @@ def apply_widget_set(self, self._initialize_widget_renderer() self._selected_widget = new_widget_set._selected_widget else: - raise TypeError( - 'Argument must be of type py_cui.widget_set.WidgetSet') + raise TypeError("Argument must be of type py_cui.widget_set.WidgetSet") - def create_new_widget_set(self, num_rows: int, - num_cols: int) -> 'py_cui.widget_set.WidgetSet': + def create_new_widget_set( + self, num_rows: int, num_cols: int + ) -> "py_cui.widget_set.WidgetSet": """Function that is used to create additional widget sets Use this function instead of directly creating widget set object instances, to allow @@ -312,7 +319,8 @@ def create_new_widget_set(self, num_rows: int, num_cols, self._logger, root=self, - simulated_terminal=self._simulated_terminal) + simulated_terminal=self._simulated_terminal, + ) # ----------------------------------------------# # Initialization functions # @@ -320,10 +328,9 @@ def create_new_widget_set(self, num_rows: int, # ----------------------------------------------# def start(self) -> None: - """Function that starts the CUI - """ + """Function that starts the CUI""" - self._logger.info(f'Starting {self._title} CUI') + self._logger.info(f"Starting {self._title} CUI") self._stopped = False curses.wrapper(self._draw) @@ -333,7 +340,7 @@ def stop(self) -> None: Callback must be a no arg method """ - self._logger.info('Stopping CUI') + self._logger.info("Stopping CUI") self._stopped = True def run_on_exit(self, command: Callable[[], Any]): @@ -371,8 +378,7 @@ def set_status_bar_text(self, text: str) -> None: self.status_bar.set_text(text) def _initialize_colors(self) -> None: - """Function for initialzing curses colors. Called when CUI is first created. - """ + """Function for initialzing curses colors. Called when CUI is first created.""" # Start colors in curses. # For each color pair in color map, initialize color combination. @@ -382,12 +388,10 @@ def _initialize_colors(self) -> None: curses.init_pair(color_pair, fg_color, bg_color) def _initialize_widget_renderer(self) -> None: - """Function that creates the renderer object that will draw each widget - """ + """Function that creates the renderer object that will draw each widget""" if self._renderer is None: - self._renderer = py_cui.renderer.Renderer(self, self._stdscr, - self._logger) + self._renderer = py_cui.renderer.Renderer(self, self._stdscr, self._logger) for widget_id in self.get_widgets().keys(): widget = self.get_widgets()[widget_id] if widget is not None: @@ -395,34 +399,35 @@ def _initialize_widget_renderer(self) -> None: widget._assign_renderer(self._renderer) except py_cui.errors.PyCUIError: self._logger.debug( - f'Renderer already assigned for widget {self.get_widgets()[widget_id]}' + f"Renderer already assigned for widget {self.get_widgets()[widget_id]}" ) try: if self._popup is not None: self._popup._assign_renderer(self._renderer) if self._logger is not None: - self._logger._live_debug_element._assign_renderer( - self._renderer) + self._logger._live_debug_element._assign_renderer(self._renderer) except py_cui.errors.PyCUIError: - self._logger.debug( - 'Renderer already assigned to popup or live-debug elements') + self._logger.debug("Renderer already assigned to popup or live-debug elements") def toggle_unicode_borders(self) -> None: - """Function for toggling unicode based border rendering - """ + """Function for toggling unicode based border rendering""" - if self._border_characters is None or self._border_characters[ - 'UP_LEFT'] == '+': - self.set_widget_border_characters('\u256d', '\u256e', '\u2570', - '\u256f', '\u2500', '\u2502') + if self._border_characters is None or self._border_characters["UP_LEFT"] == "+": + self.set_widget_border_characters( + "\u256d", "\u256e", "\u2570", "\u256f", "\u2500", "\u2502" + ) else: - self.set_widget_border_characters('+', '+', '+', '+', '-', '|') - - def set_widget_border_characters(self, upper_left_corner: str, - upper_right_corner: str, - lower_left_corner: str, - lower_right_corner: str, horizontal: str, - vertical: str) -> None: + self.set_widget_border_characters("+", "+", "+", "+", "-", "|") + + def set_widget_border_characters( + self, + upper_left_corner: str, + upper_right_corner: str, + lower_left_corner: str, + lower_right_corner: str, + horizontal: str, + vertical: str, + ) -> None: """Function that can be used to set arbitrary border characters for drawing widget borders by renderer. Parameters @@ -442,17 +447,16 @@ def set_widget_border_characters(self, upper_left_corner: str, """ self._border_characters = { - 'UP_LEFT': upper_left_corner, - 'UP_RIGHT': upper_right_corner, - 'DOWN_LEFT': lower_left_corner, - 'DOWN_RIGHT': lower_right_corner, - 'HORIZONTAL': horizontal, - 'VERTICAL': vertical + "UP_LEFT": upper_left_corner, + "UP_RIGHT": upper_right_corner, + "DOWN_LEFT": lower_left_corner, + "DOWN_RIGHT": lower_right_corner, + "HORIZONTAL": horizontal, + "VERTICAL": vertical, } - self._logger.debug( - f'Set border_characters to {self._border_characters}') + self._logger.debug(f"Set border_characters to {self._border_characters}") - def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']]: + def get_widgets(self) -> Dict[int, Optional["py_cui.widgets.Widget"]]: """Function that gets current set of widgets Returns @@ -466,14 +470,16 @@ def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']]: # Widget add functions. Each of these adds a particular type of widget # to the grid in a specified location. - def add_scroll_menu(self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0) -> 'py_cui.widgets.ScrollMenu': + def add_scroll_menu( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + ) -> "py_cui.widgets.ScrollMenu": """Function that adds a new scroll menu to the CUI grid Parameters @@ -500,30 +506,30 @@ def add_scroll_menu(self, """ id = len(self.get_widgets().keys()) - new_scroll_menu = py_cui.widgets.ScrollMenu(id, title, self._grid, row, - column, row_span, - column_span, padx, pady, - self._logger) + new_scroll_menu = py_cui.widgets.ScrollMenu( + id, title, self._grid, row, column, row_span, column_span, padx, pady, self._logger + ) if self._renderer is not None: new_scroll_menu._assign_renderer(self._renderer) self.get_widgets()[id] = new_scroll_menu if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}' + f"Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}" ) return new_scroll_menu def add_checkbox_menu( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - checked_char: str = 'X') -> 'py_cui.widgets.CheckBoxMenu': + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + checked_char: str = "X", + ) -> "py_cui.widgets.CheckBoxMenu": """Function that adds a new checkbox menu to the CUI grid Parameters @@ -553,28 +559,40 @@ def add_checkbox_menu( id = len(self.get_widgets().keys()) new_checkbox_menu = py_cui.widgets.CheckBoxMenu( - id, title, self._grid, row, column, row_span, column_span, padx, - pady, self._logger, checked_char) + id, + title, + self._grid, + row, + column, + row_span, + column_span, + padx, + pady, + self._logger, + checked_char, + ) if self._renderer is not None: new_checkbox_menu._assign_renderer(self._renderer) self.get_widgets()[id] = new_checkbox_menu if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}' + f"Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}" ) return new_checkbox_menu - def add_text_box(self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - initial_text: str = '', - password: bool = False) -> 'py_cui.widgets.TextBox': + def add_text_box( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + initial_text: str = "", + password: bool = False, + ) -> "py_cui.widgets.TextBox": """Function that adds a new text box to the CUI grid Parameters @@ -605,30 +623,41 @@ def add_text_box(self, """ id = len(self.get_widgets().keys()) - new_text_box = py_cui.widgets.TextBox(id, title, self._grid, row, - column, row_span, column_span, - padx, pady, self._logger, - initial_text, password) + new_text_box = py_cui.widgets.TextBox( + id, + title, + self._grid, + row, + column, + row_span, + column_span, + padx, + pady, + self._logger, + initial_text, + password, + ) if self._renderer is not None: new_text_box._assign_renderer(self._renderer) self.get_widgets()[id] = new_text_box if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}' + f"Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}" ) return new_text_box def add_text_block( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - initial_text: str = '') -> 'py_cui.widgets.ScrollTextBlock': + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + initial_text: str = "", + ) -> "py_cui.widgets.ScrollTextBlock": """Function that adds a new text block to the CUI grid Parameters @@ -658,26 +687,38 @@ def add_text_block( id = len(self.get_widgets().keys()) new_text_block = py_cui.widgets.ScrollTextBlock( - id, title, self._grid, row, column, row_span, column_span, padx, - pady, self._logger, initial_text) + id, + title, + self._grid, + row, + column, + row_span, + column_span, + padx, + pady, + self._logger, + initial_text, + ) if self._renderer is not None: new_text_block._assign_renderer(self._renderer) self.get_widgets()[id] = new_text_block if self._selected_widget is None: self.set_selected_widget(id) self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}' + f"Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}" ) return new_text_block - def add_label(self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0) -> 'py_cui.widgets.Label': + def add_label( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + ) -> "py_cui.widgets.Label": """Function that adds a new label to the CUI grid Parameters @@ -704,25 +745,26 @@ def add_label(self, """ id = len(self.get_widgets().keys()) - new_label = py_cui.widgets.Label(id, title, self._grid, row, column, - row_span, column_span, padx, pady, - self._logger) + new_label = py_cui.widgets.Label( + id, title, self._grid, row, column, row_span, column_span, padx, pady, self._logger + ) if self._renderer is not None: new_label._assign_renderer(self._renderer) self.get_widgets()[id] = new_label - self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') + self._logger.info(f"Adding widget {title} w/ ID {id} of type {str(type(new_label))}") return new_label - def add_block_label(self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - center: bool = True) -> 'py_cui.widgets.BlockLabel': + def add_block_label( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + center: bool = True, + ) -> "py_cui.widgets.BlockLabel": """Function that adds a new block label to the CUI grid Parameters @@ -751,26 +793,36 @@ def add_block_label(self, """ id = len(self.get_widgets().keys()) - new_label = py_cui.widgets.BlockLabel(id, title, self._grid, row, - column, row_span, column_span, - padx, pady, center, self._logger) + new_label = py_cui.widgets.BlockLabel( + id, + title, + self._grid, + row, + column, + row_span, + column_span, + padx, + pady, + center, + self._logger, + ) if self._renderer is not None: new_label._assign_renderer(self._renderer) self.get_widgets()[id] = new_label - self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') + self._logger.info(f"Adding widget {title} w/ ID {id} of type {str(type(new_label))}") return new_label def add_button( - self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - command: Callable[[], Any] = None) -> 'py_cui.widgets.Button': + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + command: Callable[[], Any] = None, + ) -> "py_cui.widgets.Button": """Function that adds a new button to the CUI grid Parameters @@ -799,31 +851,41 @@ def add_button( """ id = len(self.get_widgets().keys()) - new_button = py_cui.widgets.Button(id, title, self._grid, row, column, - row_span, column_span, padx, pady, - self._logger, command) + new_button = py_cui.widgets.Button( + id, + title, + self._grid, + row, + column, + row_span, + column_span, + padx, + pady, + self._logger, + command, + ) if self._renderer is not None: new_button._assign_renderer(self._renderer) self.get_widgets()[id] = new_button if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}' - ) + self._logger.info(f"Adding widget {title} w/ ID {id} of type {str(type(new_button))}") return new_button - def add_slider(self, - title: str, - row: int, - column: int, - row_span: int = 1, - column_span: int = 1, - padx: int = 1, - pady: int = 0, - min_val: int = 0, - max_val: int = 100, - step: int = 1, - init_val: int = 0) -> 'py_cui.controls.slider.SliderWidget': + def add_slider( + self, + title: str, + row: int, + column: int, + row_span: int = 1, + column_span: int = 1, + padx: int = 1, + pady: int = 0, + min_val: int = 0, + max_val: int = 100, + step: int = 1, + init_val: int = 0, + ) -> "py_cui.controls.slider.SliderWidget": """Function that adds a new label to the CUI grid Parameters @@ -859,17 +921,28 @@ def add_slider(self, id = len(self.get_widgets().keys()) new_slider = py_cui.controls.slider.SliderWidget( - id, title, self._grid, row, column, row_span, column_span, padx, - pady, self._logger, min_val, max_val, step, init_val) + id, + title, + self._grid, + row, + column, + row_span, + column_span, + padx, + pady, + self._logger, + min_val, + max_val, + step, + init_val, + ) if self._renderer is not None: new_slider._assign_renderer(self._renderer) self.get_widgets()[id] = new_slider - self._logger.info( - f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}' - ) + self._logger.info(f"Adding widget {title} w/ ID {id} of type {str(type(new_slider))}") return new_slider - def forget_widget(self, widget: 'py_cui.widgets.Widget') -> None: + def forget_widget(self, widget: "py_cui.widgets.Widget") -> None: """Function that is used to destroy or "forget" widgets. Forgotten widgets will no longer be drawn Parameters @@ -886,17 +959,15 @@ def forget_widget(self, widget: 'py_cui.widgets.Widget') -> None: """ if not isinstance(widget, py_cui.widgets.Widget): - raise TypeError( - 'Argument widget must by of type py_cui.widgets.Widget!') + raise TypeError("Argument widget must by of type py_cui.widgets.Widget!") elif widget.get_id() not in self.get_widgets().keys(): raise KeyError( - f'Widget with id {widget.get_id()} has already been removed from the UI!' + f"Widget with id {widget.get_id()} has already been removed from the UI!" ) else: self.get_widgets()[widget.get_id()] = None - def get_element_at_position(self, x: int, - y: int) -> Optional['py_cui.ui.UIElement']: + def get_element_at_position(self, x: int, y: int) -> Optional["py_cui.ui.UIElement"]: """Returns containing widget for character position Parameters @@ -923,8 +994,9 @@ def get_element_at_position(self, x: int, return widget return None - def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', - direction: int) -> Optional[List[int]]: + def _get_horizontal_neighbors( + self, widget: "py_cui.widgets.Widget", direction: int + ) -> Optional[List[int]]: """Gets all horizontal (left, right) neighbor widgets Parameters @@ -940,7 +1012,7 @@ def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', A list of the neighbor widget ids """ - if direction not in py_cui.keys.ARROW_KEYS: + if not direction in py_cui.keys.ARROW_KEYS: return None _, num_cols = self._grid.get_dimensions() @@ -958,24 +1030,28 @@ def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', for col in range(col_range_start, col_range_stop): for row in range(row_start, row_start + row_span): for widget_id in self.get_widgets().keys(): - # using temporary variable, for mypy - item_value = self.get_widgets()[widget_id] + item_value = self.get_widgets()[ + widget_id + ] # using temporary variable, for mypy if item_value is not None: - if item_value._is_row_col_inside( - row, col) and widget_id not in id_list: + if ( + item_value._is_row_col_inside(row, col) + and widget_id not in id_list + ): id_list.append(widget_id) if direction == py_cui.keys.KEY_LEFT_ARROW: id_list.reverse() - self._logger.debug(f'Neighbors with ids {id_list} for cell \ - {row_start},{col_start} span {row_span},{col_span}' - ) + self._logger.debug( + f"Neighbors with ids {id_list} for cell {row_start},{col_start} span {row_span},{col_span}" + ) return id_list - def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', - direction: int) -> Optional[List[int]]: + def _get_vertical_neighbors( + self, widget: "py_cui.widgets.Widget", direction: int + ) -> Optional[List[int]]: """Gets all vertical (up, down) neighbor widgets Parameters @@ -991,7 +1067,7 @@ def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', A list of the neighbor widget ids """ - if direction not in py_cui.keys.ARROW_KEYS: + if not direction in py_cui.keys.ARROW_KEYS: return None num_rows, _ = self._grid.get_dimensions() @@ -1011,16 +1087,18 @@ def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', for widget_id in self.get_widgets().keys(): item_value = self.get_widgets()[widget_id] if item_value is not None: - if item_value._is_row_col_inside( - row, col) and widget_id not in id_list: + if ( + item_value._is_row_col_inside(row, col) + and widget_id not in id_list + ): id_list.append(widget_id) if direction == py_cui.keys.KEY_UP_ARROW: id_list.reverse() - self._logger.debug(f'Neighbors with ids {id_list} for cell \ - {row_start},{col_start} span {row_span},{col_span}' - ) + self._logger.debug( + f"Neighbors with ids {id_list} for cell {row_start},{col_start} span {row_span},{col_span}" + ) return id_list @@ -1045,21 +1123,16 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: if self._selected_widget is not None: start_widget: Optional[py_cui.widgets.Widget] = self.get_widgets()[ - self._selected_widget] + self._selected_widget + ] # Find all the widgets in the given row or column neighbors: Optional[List[int]] = [] if start_widget is not None: - if direction in [ - py_cui.keys.KEY_DOWN_ARROW, py_cui.keys.KEY_UP_ARROW - ]: - neighbors = self._get_vertical_neighbors( - start_widget, direction) - elif direction in [ - py_cui.keys.KEY_RIGHT_ARROW, py_cui.keys.KEY_LEFT_ARROW - ]: - neighbors = self._get_horizontal_neighbors( - start_widget, direction) + if direction in [py_cui.keys.KEY_DOWN_ARROW, py_cui.keys.KEY_UP_ARROW]: + neighbors = self._get_vertical_neighbors(start_widget, direction) + elif direction in [py_cui.keys.KEY_RIGHT_ARROW, py_cui.keys.KEY_LEFT_ARROW]: + neighbors = self._get_horizontal_neighbors(start_widget, direction) if neighbors is None or len(neighbors) == 0: return None @@ -1067,7 +1140,7 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: # We select the best match to jump to (first neighbor) return neighbors[0] - def get_selected_widget(self) -> Optional['py_cui.widgets.Widget']: + def get_selected_widget(self) -> Optional["py_cui.widgets.Widget"]: """Function that gets currently selected widget Returns @@ -1076,11 +1149,13 @@ def get_selected_widget(self) -> Optional['py_cui.widgets.Widget']: Reference to currently selected widget object """ - if self._selected_widget is not None and self._selected_widget in self.get_widgets( - ).keys(): + if ( + self._selected_widget is not None + and self._selected_widget in self.get_widgets().keys() + ): return self.get_widgets()[self._selected_widget] else: - self._logger.warn('Selected widget ID is None or invalid') + self._logger.warn("Selected widget ID is None or invalid") return None def set_selected_widget(self, widget_id: int) -> None: @@ -1093,11 +1168,11 @@ def set_selected_widget(self, widget_id: int) -> None: """ if widget_id in self.get_widgets().keys(): - self._logger.debug(f'Setting selected widget to ID {widget_id}') + self._logger.debug(f"Setting selected widget to ID {widget_id}") self._selected_widget = widget_id else: self._logger.warn( - f'Widget w/ ID {widget_id} does not exist among current widgets.' + f"Widget w/ ID {widget_id} does not exist among current widgets." ) def lose_focus(self) -> None: @@ -1114,11 +1189,11 @@ def lose_focus(self) -> None: if widget is not None: widget.set_selected(False) else: - self._logger.info('lose_focus: Not currently in focus mode') + self._logger.info("lose_focus: Not currently in focus mode") - def move_focus(self, - widget: 'py_cui.widgets.Widget', - auto_press_buttons: bool = True) -> None: + def move_focus( + self, widget: "py_cui.widgets.Widget", auto_press_buttons: bool = True + ) -> None: """Moves focus mode to different widget Parameters @@ -1130,26 +1205,27 @@ def move_focus(self, self.lose_focus() self.set_selected_widget(widget.get_id()) - # If autofocus buttons is selected, we automatically process the button - # command and reset to overview mode - if self._auto_focus_buttons and auto_press_buttons and isinstance( - widget, py_cui.widgets.Button): + # If autofocus buttons is selected, we automatically process the button command and reset to overview mode + if ( + self._auto_focus_buttons + and auto_press_buttons + and isinstance(widget, py_cui.widgets.Button) + ): if widget.command is not None: widget.command() self._logger.debug( - f'Moved focus to button {widget.get_title()} - ran autofocus command' + f"Moved focus to button {widget.get_title()} - ran autofocus command" ) - elif self._auto_focus_buttons and isinstance(widget, - py_cui.widgets.Button): + elif self._auto_focus_buttons and isinstance(widget, py_cui.widgets.Button): self.status_bar.set_text(self._init_status_bar_text) else: widget.set_selected(True) self._in_focused_mode = True self.status_bar.set_text(widget.get_help_text()) - self._logger.debug(f'Moved focus to widget {widget.get_title()}') + self._logger.debug(f"Moved focus to widget {widget.get_title()}") def _cycle_widgets(self, reverse: bool = False) -> None: """Function that is fired if cycle key is pressed to move to next widget @@ -1181,15 +1257,12 @@ def _cycle_widgets(self, reverse: bool = False) -> None: next_widget = self.get_widgets().get(next_widget_num) if current_widget is not None and next_widget is not None: - if self._in_focused_mode and cycle_key in current_widget._key_commands.keys( - ): - # In the event that we are focusing on a widget with that key defined, we - # do not cycle. + if self._in_focused_mode and cycle_key in current_widget._key_commands.keys(): + # In the event that we are focusing on a widget with that key defined, we do not cycle. return self.move_focus(next_widget, auto_press_buttons=False) - def add_key_command(self, key: Union[int, List[int]], - command: Callable[[], Any]) -> None: + def add_key_command(self, key: Union[int, List[int]], command: Callable[[], Any]) -> None: """Function that adds a keybinding to the CUI when in overview mode Parameters @@ -1208,10 +1281,7 @@ def add_key_command(self, key: Union[int, List[int]], # Popup functions. Used to display messages, warnings, and errors to the user. - def show_message_popup(self, - title: str, - text: str, - color: int = WHITE_ON_BLACK) -> None: + def show_message_popup(self, title: str, text: str, color: int = WHITE_ON_BLACK) -> None: """Shows a message popup Parameters @@ -1224,10 +1294,10 @@ def show_message_popup(self, Popup color with format FOREGOUND_ON_BACKGROUND. See colors module. Default: WHITE_ON_BLACK. """ - self._popup = py_cui.popups.MessagePopup(self, title, text, color, - self._renderer, self._logger) - self._logger.debug( - f'Opened {str(type(self._popup))} popup with title {title}') + self._popup = py_cui.popups.MessagePopup( + self, title, text, color, self._renderer, self._logger + ) + self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") def show_warning_popup(self, title: str, text: str) -> None: """Shows a warning popup @@ -1269,17 +1339,20 @@ def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]): """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.YesNoPopup(self, title + '- (y/n)', - 'Yes - (y), No - (n)', color, - command, self._renderer, - self._logger) - self._logger.debug( - f'Opened {str(type(self._popup))} popup with title {title}') + self._popup = py_cui.popups.YesNoPopup( + self, + title + "- (y/n)", + "Yes - (y), No - (n)", + color, + command, + self._renderer, + self._logger, + ) + self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") - def show_text_box_popup(self, - title: str, - command: Callable[[str], Any], - password: bool = False): + def show_text_box_popup( + self, title: str, command: Callable[[str], Any], password: bool = False + ): """Shows a textbox popup. The 'command' parameter must be a function with a single string parameter @@ -1295,17 +1368,18 @@ def show_text_box_popup(self, """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.TextBoxPopup(self, title, color, command, - self._renderer, password, - self._logger) - self._logger.debug( - f'Opened {str(type(self._popup))} popup with title {title}') - - def show_menu_popup(self, - title: str, - menu_items: List[str], - command: Callable[[str], Any], - run_command_if_none: bool = False): + self._popup = py_cui.popups.TextBoxPopup( + self, title, color, command, self._renderer, password, self._logger + ) + self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") + + def show_menu_popup( + self, + title: str, + menu_items: List[str], + command: Callable[[str], Any], + run_command_if_none: bool = False, + ): """Shows a menu popup. The 'command' parameter must be a function with a single string parameter @@ -1323,17 +1397,21 @@ def show_menu_popup(self, """ color = WHITE_ON_BLACK - self._popup = py_cui.popups.MenuPopup(self, menu_items, title, color, - command, self._renderer, - self._logger, - run_command_if_none) - self._logger.debug( - f'Opened {str(type(self._popup))} popup with title {title}') + self._popup = py_cui.popups.MenuPopup( + self, + menu_items, + title, + color, + command, + self._renderer, + self._logger, + run_command_if_none, + ) + self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") - def show_loading_icon_popup(self, - title: str, - message: str, - callback: Callable[[], Any] = None): + def show_loading_icon_popup( + self, title: str, message: str, callback: Callable[[], Any] = None + ): """Shows a loading icon popup Parameters @@ -1348,21 +1426,18 @@ def show_loading_icon_popup(self, if callback is not None: self._post_loading_callback = callback - self._logger.debug( - f'Post loading callback funciton set to {str(callback)}') + self._logger.debug(f"Post loading callback funciton set to {str(callback)}") color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingIconPopup(self, title, message, - color, self._renderer, - self._logger) - self._logger.debug( - f'Opened {str(type(self._popup))} popup with title {title}') + self._popup = py_cui.popups.LoadingIconPopup( + self, title, message, color, self._renderer, self._logger + ) + self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") - def show_loading_bar_popup(self, - title: str, - num_items: List[int], - callback: Callable[[], Any] = None) -> None: + def show_loading_bar_popup( + self, title: str, num_items: List[int], callback: Callable[[], Any] = None + ) -> None: """Shows loading bar popup. Use 'increment_loading_bar' to show progress @@ -1379,23 +1454,23 @@ def show_loading_bar_popup(self, if callback is not None: self._post_loading_callback = callback - self._logger.debug( - f'Post loading callback funciton set to {str(callback)}') + self._logger.debug(f"Post loading callback funciton set to {str(callback)}") color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingBarPopup(self, title, num_items, - color, self._renderer, - self._logger) - self._logger.debug( - f'Opened {str(type(self._popup))} popup with title {title}') - - def show_form_popup(self, - title: str, - fields: List[str], - passwd_fields: List[str] = [], - required: List[str] = [], - callback: Callable[[], Any] = None) -> None: + self._popup = py_cui.popups.LoadingBarPopup( + self, title, num_items, color, self._renderer, self._logger + ) + self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") + + def show_form_popup( + self, + title: str, + fields: List[str], + passwd_fields: List[str] = [], + required: List[str] = [], + callback: Callable[[], Any] = None, + ) -> None: """Shows form popup. Used for inputting several fields worth of values @@ -1415,23 +1490,31 @@ def show_form_popup(self, """ self._popup = py_cui.dialogs.form.FormPopup( - self, fields, passwd_fields, required, {}, title, - py_cui.WHITE_ON_BLACK, self._renderer, self._logger) + self, + fields, + passwd_fields, + required, + {}, + title, + py_cui.WHITE_ON_BLACK, + self._renderer, + self._logger, + ) if callback is not None: self._popup.set_on_submit_action(callback) - self._logger.debug( - f'Form enter callback funciton set to {str(callback)}') - - self._logger.debug( - f'Opened {str(type(self._popup))} popup with title {title}') - - def show_filedialog_popup(self, - popup_type: str = 'openfile', - initial_dir: str = '.', - callback: Callable[[], Any] = None, - ascii_icons: bool = True, - limit_extensions: List[str] = []) -> None: + self._logger.debug(f"Form enter callback funciton set to {str(callback)}") + + self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") + + def show_filedialog_popup( + self, + popup_type: str = "openfile", + initial_dir: str = ".", + callback: Callable[[], Any] = None, + ascii_icons: bool = True, + limit_extensions: List[str] = [], + ) -> None: """Shows form popup. Used for inputting several fields worth of values @@ -1451,21 +1534,26 @@ def show_filedialog_popup(self, """ self._popup = py_cui.dialogs.filedialog.FileDialogPopup( - self, callback, initial_dir, popup_type, ascii_icons, - limit_extensions, py_cui.WHITE_ON_BLACK, self._renderer, - self._logger) + self, + callback, + initial_dir, + popup_type, + ascii_icons, + limit_extensions, + py_cui.WHITE_ON_BLACK, + self._renderer, + self._logger, + ) - self._logger.debug( - f'Opened {str(type(self._popup))} popup with type {popup_type}') + self._logger.debug(f"Opened {str(type(self._popup))} popup with type {popup_type}") def increment_loading_bar(self) -> None: - """Increments progress bar if loading bar popup is open - """ + """Increments progress bar if loading bar popup is open""" if self._popup is not None: self._popup._increment_counter() else: - self._logger.warn('No popup is currently opened.') + self._logger.warn("No popup is currently opened.") def stop_loading_popup(self) -> None: """Leaves loading state, and closes popup. @@ -1475,11 +1563,10 @@ def stop_loading_popup(self) -> None: self._loading = False self.close_popup() - self._logger.debug('Stopping open loading popup') + self._logger.debug("Stopping open loading popup") def close_popup(self) -> None: - """Closes the popup, and resets focus - """ + """Closes the popup, and resets focus""" self.lose_focus() self._popup = None @@ -1499,18 +1586,15 @@ def _refresh_height_width(self) -> None: height = self._simulated_terminal[0] width = self._simulated_terminal[1] - height = height - self.title_bar.get_height( - ) - self.status_bar.get_height() - 2 + height = height - self.title_bar.get_height() - self.status_bar.get_height() - 2 - self._logger.debug( - f'Resizing CUI to new dimensions {height} by {width}') + self._logger.debug(f"Resizing CUI to new dimensions {height} by {width}") self._height = height self._width = width self._grid.update_grid_height_width(self._height, self._width) for widget_id in self.get_widgets().keys(): - widget = self.get_widgets()[ - widget_id] # using temp variable, for mypy + widget = self.get_widgets()[widget_id] # using temp variable, for mypy if widget is not None: widget.update_height_width() if self._popup is not None: @@ -1532,8 +1616,7 @@ def get_absolute_size(self) -> Tuple[int, int]: # Draw Functions. Function for drawing widgets, status bars, and popups def _draw_widgets(self) -> None: - """Function that draws all of the widgets to the screen - """ + """Function that draws all of the widgets to the screen""" for widget_id in self.get_widgets().keys(): if widget_id != self._selected_widget: @@ -1550,7 +1633,7 @@ def _draw_widgets(self) -> None: if self._logger is not None and self._logger.is_live_debug_enabled(): self._logger.draw_live_debug() - self._logger.info('Drew widgets') + self._logger.info("Drew widgets") def _draw_status_bars(self, stdscr, height: int, width: int) -> None: """Draws status bar and title bar @@ -1567,8 +1650,7 @@ def _draw_status_bars(self, stdscr, height: int, width: int) -> None: if self.status_bar is not None and self.status_bar.get_height() > 0: stdscr.attron(curses.color_pair(self.status_bar.get_color())) - stdscr.addstr(height + 3, 0, - fit_text(width, self.status_bar.get_text())) + stdscr.addstr(height + 3, 0, fit_text(width, self.status_bar.get_text())) stdscr.attroff(curses.color_pair(self.status_bar.get_color())) if self.title_bar is not None and self.title_bar.get_height() > 0: @@ -1589,12 +1671,12 @@ def _display_window_warning(self, stdscr, error_info: str) -> None: stdscr.clear() stdscr.attron(curses.color_pair(RED_ON_BLACK)) - stdscr.addstr(0, 0, 'Error displaying CUI!!!') - stdscr.addstr(1, 0, f'Error Type: {error_info}') - stdscr.addstr(2, 0, 'Most likely terminal dimensions are too small.') + stdscr.addstr(0, 0, "Error displaying CUI!!!") + stdscr.addstr(1, 0, f"Error Type: {error_info}") + stdscr.addstr(2, 0, "Most likely terminal dimensions are too small.") stdscr.attroff(curses.color_pair(RED_ON_BLACK)) stdscr.refresh() - self._logger.error(f'Encountered error -> {error_info}') + self._logger.error(f"Encountered error -> {error_info}") def _handle_key_presses(self, key_pressed: int) -> None: """Function that handles all main loop key presses. @@ -1605,8 +1687,7 @@ def _handle_key_presses(self, key_pressed: int) -> None: The key being pressed """ - # Selected widget represents which widget is being hovered over, though - # not necessarily in focus mode + # Selected widget represents which widget is being hovered over, though not necessarily in focus mode if self._selected_widget is None: return @@ -1631,19 +1712,21 @@ def _handle_key_presses(self, key_pressed: int) -> None: self._in_focused_mode = False selected_widget.set_selected(False) self._logger.debug( - f'Exiting focus mode on widget {selected_widget.get_title()}' + f"Exiting focus mode on widget {selected_widget.get_title()}" ) else: # widget handles remaining py_cui.keys self._logger.debug( - f'Widget {selected_widget.get_title()} handling {key_pressed} key' + f"Widget {selected_widget.get_title()} handling {key_pressed} key" ) 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 + # 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( + if ( + key_pressed == py_cui.keys.KEY_ENTER + and self._selected_widget is not None + and selected_widget.is_selectable() ): self.move_focus(selected_widget) @@ -1651,12 +1734,11 @@ def _handle_key_presses(self, key_pressed: int) -> None: if key_pressed == key: command = self._keybindings[key] self._logger.info( - f'Detected binding for key {key_pressed}, running command {command.__name__}' + f"Detected binding for key {key_pressed}, running command {command.__name__}" ) command() - # If not in focus mode, use the arrow py_cui.keys to move around the - # selectable widgets. + # If not in focus mode, use the arrow py_cui.keys to move around the selectable widgets. neighbor = None if key_pressed in py_cui.keys.ARROW_KEYS: neighbor = self._check_if_neighbor_exists(key_pressed) @@ -1664,13 +1746,11 @@ def _handle_key_presses(self, key_pressed: int) -> None: self.set_selected_widget(neighbor) widget = self.get_widgets()[self._selected_widget] if widget is not None: - self._logger.debug( - f'Navigated to neighbor widget {widget.get_title()}') + self._logger.debug(f"Navigated to neighbor widget {widget.get_title()}") # if we have a popup, that takes key control from both overview and focus mode elif self._popup is not None: - self._logger.debug( - f'Popup {self._popup.get_title()} handling key {key_pressed}') + self._logger.debug(f"Popup {self._popup.get_title()} handling key {key_pressed}") self._popup._handle_key_press(key_pressed) def _draw(self, stdscr) -> None: @@ -1704,9 +1784,10 @@ def _draw(self, stdscr) -> None: if self._border_characters is not None and self._renderer is not None: 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: + # 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 + ): try: # If we call stop, we want to break out of the main draw loop @@ -1728,43 +1809,41 @@ def _draw(self, stdscr) -> None: try: self._refresh_height_width() except py_cui.errors.PyCUIOutOfBoundsError as e: - self._logger.info('Resized terminal too small') + self._logger.info("Resized terminal too small") self._display_window_warning(stdscr, str(e)) - # Here we handle mouse click events globally, or pass them to the UI - # element to handle + # Here we handle mouse click events globally, or pass them to the UI element to handle elif key_pressed == curses.KEY_MOUSE: - self._logger.info('Detected mouse click') + self._logger.info("Detected mouse click") valid_mouse_event = True try: id, x, y, _, mouse_event = curses.getmouse() except curses.error as e: valid_mouse_event = False - self._logger.error( - f'Failed to handle mouse event: {str(e)}') + self._logger.error(f"Failed to handle mouse event: {str(e)}") if valid_mouse_event: in_element = self.get_element_at_position(x, y) - # In first case, we click inside already selected widget, pass click - # for processing + # In first case, we click inside already selected widget, pass click for processing if in_element is not None: self._logger.info( - f'handling mouse press for elem: {in_element.get_title()}' + f"handling mouse press for elem: {in_element.get_title()}" ) in_element._handle_mouse_press(x, y, mouse_event) # Otherwise, if not a popup, select the clicked on widget elif in_element is not None and not isinstance( - in_element, py_cui.popups.Popup): + in_element, py_cui.popups.Popup + ): self.move_focus(in_element) in_element._handle_mouse_press(x, y, mouse_event) # If we have a post_loading_callback, fire it here if self._post_loading_callback is not None and not self._loading: self._logger.debug( - f'Firing post-loading callback function {self._post_loading_callback.__name__}' + f"Firing post-loading callback function {self._post_loading_callback.__name__}" ) self._post_loading_callback() self._post_loading_callback = None @@ -1791,10 +1870,10 @@ def _draw(self, stdscr) -> None: self._logger.draw_live_debug() except curses.error as e: - self._logger.error('Curses error while drawing TUI') + self._logger.error("Curses error while drawing TUI") self._display_window_warning(stdscr, str(e)) except py_cui.errors.PyCUIOutOfBoundsError as e: - self._logger.error('Resized terminal too small') + self._logger.error("Resized terminal too small") self._display_window_warning(stdscr, str(e)) # Refresh the screen @@ -1804,25 +1883,23 @@ def _draw(self, stdscr) -> None: 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. + # 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: - self._logger.info('Waiting for next keypress') + self._logger.info("Waiting for next keypress") key_pressed = stdscr.getch() except KeyboardInterrupt: - self._logger.info('Detect Keyboard Interrupt, Exiting...') + self._logger.info("Detect Keyboard Interrupt, Exiting...") self._stopped = True stdscr.erase() stdscr.refresh() curses.endwin() if self._on_stop is not None: - self._logger.debug( - f'Firing onstop function {self._on_stop.__name__}') + self._logger.debug(f"Firing onstop function {self._on_stop.__name__}") self._on_stop() def __format__(self, fmt): @@ -1834,8 +1911,8 @@ def __format__(self, fmt): The format to override """ - out = '' + out = "" for widget_id in self.get_widgets().keys(): if self.get_widgets()[widget_id] is not None: - out += f'{self.get_widgets()[widget_id].get_title()}\n' + out += f"{self.get_widgets()[widget_id].get_title()}\n" return out diff --git a/requirements_dev.txt b/requirements_dev.txt index 10ac091..40461ae 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -4,4 +4,4 @@ wheel npdoc2md mkdocs windows-curses ; platform_system=="Windows" -yapf +black diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 53d642b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[style] -based_on_style = pep8 -column_limit = 95 -use_tabs = true From 1338fc2273c89d9dd6d190e67f65a8cbe42fc1bd Mon Sep 17 00:00:00 2001 From: jwlodek Date: Fri, 7 Jan 2022 10:56:32 -0500 Subject: [PATCH 22/30] Adding missing return statement to add_button function --- py_cui/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 3dff5a0..9ecf439 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -754,15 +754,15 @@ def add_button(self, title: str, row: int, column: int, row_span: int = 1, colum A reference to the created button object. """ - self.add_custom_widget(py_cui.widgets.Button, - title, - row, - column, - row_span, - column_span, - padx, - pady, - command) + return self.add_custom_widget(py_cui.widgets.Button, + title, + row, + column, + row_span, + column_span, + padx, + pady, + command) def add_slider(self, title: str, row: int, column: int, row_span: int=1, From 6a4ec1663d4567347b05a2759937ea04074d9cc0 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Tue, 12 Apr 2022 10:23:32 -0400 Subject: [PATCH 23/30] Adding mouse controls for slider widget --- py_cui/controls/slider.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/py_cui/controls/slider.py b/py_cui/controls/slider.py index b4b742c..2676c87 100644 --- a/py_cui/controls/slider.py +++ b/py_cui/controls/slider.py @@ -2,6 +2,7 @@ import py_cui.widgets import py_cui.popups import py_cui.errors +import py_cui.keys class SliderImplementation(py_cui.ui.UIImplementation): @@ -274,5 +275,38 @@ def _handle_key_press(self, key_pressed: int) -> None: self.update_slider_value(1) + def _handle_mouse_press(self, x: int, y: int, mouse_event: int): + """Override of base class handle mouse press function + + Parameters + ---------- + x : int + x-position of the mouse event in the terminal + y : int + y position of the mouse event in the terminal + mouse_event : int + Mouse event type code + """ + + super()._handle_mouse_press(x, y, mouse_event) + if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK or mouse_event == py_cui.keys.LEFT_MOUSE_RELEASED: + x_start, _ = self.get_absolute_start_pos() + x_stop, _ = self.get_absolute_stop_pos() + + # Get first x postion where the progress bar exists + x_start = x_start + 2 + self._padx + + # Get last x postion where the progress bar exists + x_stop = x_stop - 2 - self._padx + + if x < x_start or x > x_stop: + pass + else: + num_characters_to_press = x - x_start + prop_of_slider = num_characters_to_press / (x_stop - x_start) + slider_val_width = self._max_val - self._min_val + self._cur_val = int(prop_of_slider * slider_val_width) + self._min_val + + class SliderPopup(py_cui.popups.Popup, SliderImplementation): pass From 8236f9be86fa0e6ad077e0c21f922767391d2638 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Tue, 12 Apr 2022 11:04:06 -0400 Subject: [PATCH 24/30] Round up on slider mouse presses for more expected behavior --- py_cui/controls/slider.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py_cui/controls/slider.py b/py_cui/controls/slider.py index 2676c87..58ecfa0 100644 --- a/py_cui/controls/slider.py +++ b/py_cui/controls/slider.py @@ -307,6 +307,10 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int): slider_val_width = self._max_val - self._min_val self._cur_val = int(prop_of_slider * slider_val_width) + self._min_val + # Round up. Avoids awkward situation where mousepress is to the right of the slider pos + if not (prop_of_slider * slider_val_width).is_integer(): + self._cur_val += 1 + class SliderPopup(py_cui.popups.Popup, SliderImplementation): pass From 49a6fb41663afaa3fd7a25808b0d3258843f52aa Mon Sep 17 00:00:00 2001 From: jwlodek Date: Tue, 12 Apr 2022 11:09:57 -0400 Subject: [PATCH 25/30] Add option for setting initial default text for textbox popups --- examples/popups_example.py | 2 +- py_cui/__init__.py | 4 ++-- py_cui/popups.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/popups_example.py b/examples/popups_example.py index ca92849..c430b2a 100644 --- a/examples/popups_example.py +++ b/examples/popups_example.py @@ -69,7 +69,7 @@ def quit_cui(self, to_quit): def show_text_box(self): # Here, reset title is a function that takes a string parameter, which will be the user entered string - self.master.show_text_box_popup('Please enter a new window title', self.reset_title) + self.master.show_text_box_popup('Please enter a new window title', self.reset_title, initial_text='New Title') def reset_title(self, new_title): self.master.set_title(new_title) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 0d43680..e395d4d 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1351,7 +1351,7 @@ def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]): self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") def show_text_box_popup( - self, title: str, command: Callable[[str], Any], password: bool = False + self, title: str, command: Callable[[str], Any], initial_text = '', password: bool = False ): """Shows a textbox popup. @@ -1369,7 +1369,7 @@ def show_text_box_popup( color = WHITE_ON_BLACK self._popup = py_cui.popups.TextBoxPopup( - self, title, color, command, self._renderer, password, self._logger + self, title, initial_text, color, command, self._renderer, password, self._logger ) self._logger.debug(f"Opened {str(type(self._popup))} popup with title {title}") diff --git a/py_cui/popups.py b/py_cui/popups.py index 3d21258..620d601 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -210,12 +210,12 @@ class TextBoxPopup(Popup, py_cui.ui.TextBoxImplementation): The command to run when enter is pressed """ - def __init__(self, root, title, color, command, renderer, password, logger): + def __init__(self, root, title, initial_text, color, command, renderer, password, logger): """Initializer for textbox popup. Uses TextBoxImplementation as base """ - Popup.__init__(self, root, title, '', color, renderer, logger) - py_cui.ui.TextBoxImplementation.__init__(self, '', password, logger) + Popup.__init__(self, root, title, initial_text, color, renderer, logger) + py_cui.ui.TextBoxImplementation.__init__(self, initial_text, password, logger) self._command = command self.update_height_width() From d90b251daab7f22212fe53a9dc616d35ddc47fd3 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Fri, 6 May 2022 11:15:53 -0400 Subject: [PATCH 26/30] Update adding widget docs with add_custom_widget function --- docs/developers.md | 44 ++++++++++++-------------------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/docs/developers.md b/docs/developers.md index a896019..f2e202e 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -146,8 +146,11 @@ class ScrollMenu(Widget, py_cui.ui.MenuImplementation): super().draw() ``` + The `_handle_key_press` and `_draw` functions must be extended for your new widget. You may leave the `_handle_key_press` as above, if you don't require any keybindings for the widget. The `_draw` function must extended, as the base class does no drawing itself, instead just setting up color rules. +In addition, any widget class you develop must have the arguments `(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger)` in it's initializer, in that order, so that the `add_custom_widget` function will work. Additional arguments or keyword arguments can follow these. + **Step 3 - Add Key Bindings** Next, add any default key bindings you wish to have for the widget when in focus mode. In the case of the scroll menu, we wish for the arrow keys to scroll up and down, so we extend the `handle_key_press` function: @@ -199,43 +202,20 @@ def _draw(self): Note that you should call `super()._draw()` and `self._renderer.set_color_mode(self._color)` at the start of the function (to initialize color modes), and `self._renderer.unset_color_mode(self._color)` and `self._renderer.reset_cursor(self)` to remove color settings, and place the cursor in the correct location. -**Step 5 - Add a function to `PyCUI` class to add the widget** +**Step 5 - Use the add_custom_widget function to add your widget to your TUI** + +Finally, you are ready to add an instance of your widget to the TUI. To do this, use the `add_custom_widget` function. See the below example adding our above widget. Optionally, you may want to add a conveniance function specifically meant to add instances of your widget to the TUI. In our example it would be `add_scroll_menu`, which can be found in `py_cui/__init__.py`. -Finally, add a function to the `PyCUI` class in `__init__.py` that will add the widget to the CUI. In our case we write the following: ```Python -def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0): - - id = f'Widget{len(self.get_widgets().keys())}' - new_scroll_menu = widgets.ScrollMenu( id, - title, - self._grid, - row, - column, - row_span, - column_span, - padx, - pady, - self._logger) - self.get_widgets()[id] = new_scroll_menu - if self._selected_widget is None: - self.set_selected_widget(id) - self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}' - return new_scroll_menu -``` -The function must: - -* Create an id titled 'Widget####' where #### is replaced with the number of widget -* Add the widget to the PyCUI widgets dict with the ID as a key -* If there is no selected widget, make this new widget the selected one -* Return a reference to the widget +# add_custom_widget(Widget class, title, row, column, rowspan, colspan, padx, pady) +my_widget = root.add_custom_widget(py_cui.widgets.ScrollMenu, "My Title", 1, 1, 1, 1, 1, 1) +``` -**That's it!** +Note the `add_custom_widget` function signature expects your class that extends `Widget` as the first argument, followed by critical widget information. You can also add additional `*args` and `**kwargs` to the funtion call. The function will return an instance of the initial class with all the remaining arguments passed in in order. -Your widget is now ready to be added to the CUI! Simply call the add function with appropriate parameters on the root `PyCUI` window: +**That's it!** -```Python -root.add_scroll_menu('Demo', 1, 1) -``` +Your widget should now be added to the CUI! ### Adding a new Popup From 9084422ab44793e77a39e97840c84b96d76526f6 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Fri, 1 Jul 2022 21:15:45 -0400 Subject: [PATCH 27/30] Fix issue when clearing text block widget after scrolling below viewport --- py_cui/ui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py_cui/ui.py b/py_cui/ui.py index a5284ce..4443d47 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -1201,6 +1201,8 @@ def clear(self) -> None: self._cursor_x = self._cursor_max_left self._cursor_y = self._cursor_max_up + self._viewport_y_start = 0 + self._viewport_x_start = 0 self._cursor_text_pos_x = 0 self._cursor_text_pos_y = 0 self._text_lines = [] From 246f13f88dc0b4c9628be8c5961f7f1036a47eaf Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Fri, 1 Jul 2022 22:08:15 -0400 Subject: [PATCH 28/30] Add function for clearing color rules from widget --- py_cui/widgets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 8300f6f..19256c4 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -170,6 +170,13 @@ def add_text_color_rule(self, regex: str, color: int, rule_type: str, match_type self._text_color_rules.append(new_color_rule) + def clear_color_rules(self): + """Removes all configured color rules for the widget + """ + + self._text_color_rules.clear() + + def get_absolute_start_pos(self) -> Tuple[int,int]: """Gets the absolute position of the widget in characters. Override of base class function From 4cf51cae490e970b98b0391b4583c15bfcaf7eb1 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Fri, 1 Jul 2022 22:41:38 -0400 Subject: [PATCH 29/30] Adding some simple functions that allow for having a scroll menu stick to bottom - useful for logs for example. --- py_cui/ui.py | 11 +++++++++++ py_cui/widgets.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/py_cui/ui.py b/py_cui/ui.py index 4443d47..06ae250 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -683,6 +683,14 @@ def __init__(self, logger): self._page_scroll_len = 5 self._view_items = [] self._on_selection_change: Optional[Callable[[Any],Any]] = None + self._stick_to_bottom = False + + + def toggle_stick_to_bottom(self): + """Toggle option for keeping the viewport at the bottom of the items + """ + + self._stick_to_bottom = not self._stick_to_bottom def clear(self) -> None: @@ -853,6 +861,9 @@ def add_item(self, item: Any) -> None: self._logger.debug(f'Adding item {str(item)} to menu') self._view_items.append(item) + if self._stick_to_bottom: + self.set_selected_item_index(len(self._view_items) - 1) + def add_item_list(self, item_list: List[Any]) -> None: diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 19256c4..3757055 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -541,6 +541,20 @@ def _handle_key_press(self, key_pressed: int) -> None: self._process_selection_change_event() + def add_item(self, item): + """Override of add_item function, allows for sticking to bottom + + Parameters + ---------- + item : Object + Object to add to the menu. Must have implemented __str__ function + """ + + super().add_item(item) + if self._stick_to_bottom and self._top_view < (len(self._view_items) - self.get_viewport_height() - 1): + self._top_view = len(self._view_items) - self.get_viewport_height() - 1 + + def _draw(self) -> None: """Overrides base class draw function """ @@ -549,6 +563,7 @@ def _draw(self) -> None: self._renderer.set_color_mode(self._color) self._renderer.draw_border(self) counter = self._pady + 1 + line_counter = 0 for item in self._view_items: line = str(item) From d5534cbaa7c1687bd5effafdd6cd13ebd2620175 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Wed, 28 Sep 2022 13:10:18 -0400 Subject: [PATCH 30/30] Fix HOME and END behavior for textbox popups --- py_cui/popups.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py_cui/popups.py b/py_cui/popups.py index 620d601..f79d972 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -230,6 +230,7 @@ def update_height_width(self) -> None: height, width = self.get_absolute_dimensions() self._cursor_text_pos = 0 self._cursor_x = start_x + 2 + padx + self._initial_cursor = self._cursor_x self._cursor_max_left = self._cursor_x self._cursor_max_right = start_x + width - 1 - pady self._cursor_y = start_y + int(height / 2) + 1