From d0180feb6a5495eab65dc7bb1d2f2ba192498be3 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Mon, 19 Apr 2021 17:20:17 -0400 Subject: [PATCH 01/40] Live debug prints log messages, can be toggled --- py_cui/__init__.py | 23 ++++++++++++++++ py_cui/debug.py | 67 +++++++++++++++++++++++++++++----------------- 2 files changed, 66 insertions(+), 24 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index efd2a47..1f828dd 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -237,6 +237,18 @@ def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging print('Failed to initialize logger: {}'.format(str(e))) + def is_live_debug_mode_active(self): + if self._logger is None: + return False + else: + return self._logger.is_live_debug_enabled() + + + def toggle_live_debug_mode(self): + if self._logger is not None: + self._logger.toggle_live_debug() + + def apply_widget_set(self, new_widget_set): """Function that replaces all widgets in a py_cui with those of a different widget set @@ -1432,6 +1444,12 @@ def _handle_key_presses(self, key_pressed): selected_widget = self.get_widgets()[self._selected_widget] + # If logging is enabled, the Ctrl + D key code will enable "live-debug" + # mode, where debug messages are printed on the screen + if self._logger is not None: + if key_pressed == py_cui.keys.KEY_CTRL_D: + self.toggle_live_debug_mode() + # If we are in focus mode, the widget has all of the control of the keyboard except # for the escape key, which exits focus mode. if self._in_focused_mode and self._popup is None: @@ -1573,6 +1591,11 @@ def _draw(self, stdscr): # draw the popup if required if self._popup is not None: self._popup._draw() + + # If we are in live debug mode, we draw our debug messages + if self.is_live_debug_mode_active(): + self._logger.draw_live_debug() + except curses.error as e: self._logger.error('Curses error while drawing TUI') self._display_window_warning(stdscr, str(e)) diff --git a/py_cui/debug.py b/py_cui/debug.py index 2b04f8b..e51b67e 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -100,8 +100,34 @@ def __init__(self, name): super(PyCUILogger, self).__init__(name) self._live_debug_level = logging.ERROR self._live_debug_enabled = False + self._debug_msg_buffer = [] + self._buffer_size = 100 + self._num_view_msg = 10 + self._live_debug_alignment = 'TOP' + self._current_bottom_debug_msg = 0 + def set_live_debug_alignment(self, alignment = 'TOP'): + self._live_debug_alignment = alignment + + + def is_live_debug_enabled(self): + return self._live_debug_enabled + + def toggle_live_debug(self): + self._live_debug_enabled = not self._live_debug_enabled + + + def draw_live_debug(self): + num_messages_to_display = self._num_view_msg + if self.py_cui_root._height < num_messages_to_display: + num_messages_to_display = self.py_cui_root._height - 1 + if len(self._debug_msg_buffer) < num_messages_to_display: + num_messages_to_display = len(self._debug_msg_buffer) - 1 + + for i in range(0, num_messages_to_display): + self.py_cui_root._stdscr.addstr(num_messages_to_display - i + 1, 1, self._debug_msg_buffer[self._current_bottom_debug_msg - i]) + def _assign_root_window(self, py_cui_root): """Attaches logger to the root window for live debugging """ @@ -117,7 +143,16 @@ def _get_debug_text(self, text): """ func = inspect.currentframe().f_back.f_back.f_code - return "{}: Function {} in {}:{}".format(text, func.co_name, os.path.basename(func.co_filename), func.co_firstlineno) + return "{} -> {}:{} - {}".format(text, + func.co_name, + os.path.basename(func.co_filename), + func.co_firstlineno) + + + def _add_msg_to_buffer(self, msg): + if len(self._debug_msg_buffer) == self._buffer_size: + self._debug_msg_buffer.pop(0) + self._debug_msg_buffer.append(msg) def info(self, text): @@ -130,6 +165,7 @@ def info(self, text): """ debug_text = self._get_debug_text(text) + self._add_msg_to_buffer(debug_text) super().info(debug_text) @@ -144,11 +180,8 @@ def debug(self, text): debug_text = self._get_debug_text(text) if self._live_debug_level == logging.DEBUG and self._live_debug_enabled: - if self.py_cui_root is not None: - self.py_cui_root.status_bar.set_text(debug_text) - super().debug(debug_text) - else: - super().debug(debug_text) + self._add_msg_to_buffer(debug_text) + super().debug(debug_text) def warn(self, text): @@ -162,11 +195,8 @@ def warn(self, text): debug_text = self._get_debug_text(text) if self._live_debug_level < logging.WARN and self._live_debug_enabled: - if self.py_cui_root is not None: - self.py_cui_root.status_bar.set_text(debug_text) - super().debug(debug_text) - else: - super().warn(debug_text) + self._add_msg_to_buffer(debug_text) + super().warn(debug_text) def error(self, text): @@ -180,16 +210,5 @@ def error(self, text): debug_text = self._get_debug_text(text) if self._live_debug_level < logging.ERROR and self._live_debug_enabled: - if self.py_cui_root is not None: - self.py_cui_root.status_bar.set_text(debug_text) - super().debug(debug_text) - else: - super().error(debug_text) - - - def toggle_live_debug(self, level=logging.ERROR): - """Toggles live debugging mode - """ - - self._live_debug_enabled = not self._live_debug_enabled - self._live_debug_level = level \ No newline at end of file + self._add_msg_to_buffer(debug_text) + super().error(debug_text) From b12160a439f52e473f4485b1989a6ad05134f096 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sun, 9 May 2021 12:42:06 -0400 Subject: [PATCH 02/40] Add forget widget function, convert to f strings, formatting --- py_cui/__init__.py | 238 ++++++++++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 90 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 6ff77dd..4fdf3f7 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -139,9 +139,10 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, # Add status and title bar self.title_bar = py_cui.statusbar.StatusBar(self._title, BLACK_ON_WHITE) exit_key_char = py_cui.keys.get_char_from_ascii(exit_key) - self._init_status_bar_text = 'Press - {} - to exit. Arrow Keys to move ' \ + self._init_status_bar_text = f'Press - {exit_key_char} - to exit. Arrow Keys to move ' \ 'between widgets. Enter to enter focus ' \ - 'mode.'.format(exit_key_char) + 'mode.' + self.status_bar = py_cui.statusbar.StatusBar(self._init_status_bar_text, BLACK_ON_WHITE) @@ -234,7 +235,7 @@ def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging py_cui.debug._enable_logging(self._logger, filename=log_file_path, logging_level=logging_level) self._logger.info('Initialized logger') except PermissionError as e: - print('Failed to initialize logger: {}'.format(str(e))) + print(f'Failed to initialize logger: {str(e)}') def apply_widget_set(self, new_widget_set): @@ -313,7 +314,7 @@ def start(self): """Function that starts the CUI """ - self._logger.info('Starting {} CUI'.format(self._title)) + self._logger.info(f'Starting {self._title} CUI') curses.wrapper(self._draw) @@ -383,8 +384,9 @@ def _initialize_widget_renderer(self): if self._renderer is None: self._renderer = py_cui.renderer.Renderer(self, self._stdscr, self._logger) - for widget_id in self._widgets.keys(): - self._widgets[widget_id]._assign_renderer(self._renderer) + for widget_id in self.get_widgets().keys(): + if self.get_widgets()[widget_id] is not None: + self.get_widgets()[widget_id]._assign_renderer(self._renderer) if self._popup is not None: self._popup._assign_renderer(self._renderer) @@ -426,7 +428,7 @@ def set_widget_border_characters(self, upper_left_corner, upper_right_corner, lo 'HORIZONTAL': horizontal, 'VERTICAL': vertical } - self._logger.info('Set border_characters to {}'.format(self._border_characters)) + self._logger.debug(f'Set border_characters to {self._border_characters}') def get_widgets(self): @@ -469,7 +471,7 @@ def add_scroll_menu(self, title, row, column, row_span=1, column_span=1, padx=1, A reference to the created scroll menu object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_scroll_menu = py_cui.widgets.ScrollMenu(id, title, self._grid, @@ -481,10 +483,10 @@ def add_scroll_menu(self, title, row, column, row_span=1, column_span=1, padx=1, pady, self._logger) new_scroll_menu._assign_renderer(self._renderer) - self._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('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_scroll_menu)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}') return new_scroll_menu @@ -516,7 +518,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= A reference to the created checkbox object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self._widgets.keys()) new_checkbox_menu = py_cui.widgets.CheckBoxMenu(id, title, self._grid, @@ -532,7 +534,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= self._widgets[id] = new_checkbox_menu if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, 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 @@ -566,7 +568,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = A reference to the created textbox object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self._widgets.keys()) new_text_box = py_cui.widgets.TextBox(id, title, self._grid, @@ -581,7 +583,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = self._widgets[id] = new_text_box if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, 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 @@ -613,7 +615,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx A reference to the created textblock object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self._widgets.keys()) new_text_block = py_cui.widgets.ScrollTextBlock(id, title, self._grid, @@ -629,7 +631,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx self._widgets[id] = new_text_block if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, 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 @@ -659,7 +661,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, A reference to the created label object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self._widgets.keys()) new_label = py_cui.widgets.Label(id, title, self._grid, @@ -672,7 +674,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, self._logger) new_label._assign_renderer(self._renderer) self._widgets[id] = new_label - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_label)))) + self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') return new_label @@ -704,7 +706,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad A reference to the created block label object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self._widgets.keys()) new_label = py_cui.widgets.BlockLabel(id, title, self._grid, @@ -718,7 +720,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad self._logger) new_label._assign_renderer(self._renderer) self._widgets[id] = new_label - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_label)))) + self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') return new_label @@ -750,7 +752,7 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 A reference to the created button object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self._widgets.keys()) new_button = py_cui.widgets.Button(id, title, self._grid, @@ -766,7 +768,7 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 self._widgets[id] = new_button if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_button)))) + self._logger.info(f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') return new_button @@ -806,7 +808,7 @@ def add_slider(self, title, row, column, row_span=1, A reference to the created slider object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self._widgets.keys()) new_slider = py_cui.controls.slider.SliderWidget(id, title, self._grid, @@ -823,11 +825,34 @@ def add_slider(self, title, row, column, row_span=1, init_val) new_slider._assign_renderer(self._renderer) self._widgets[id] = new_slider - self._logger.info('Adding widget {} w/ ID {} of type {}' - .format(title, id, 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: + """Function that is used to destroy or "forget" widgets. Forgotten widgets will no longer be drawn + + Parameters + ---------- + widget : py_cui.widgets.Widget + Widget to remove from the UI + + Raises + ------ + TypeError + If input parameter is not of the py_cui widget type + KeyError + If input widget does not exist in the current UI or has already been removed. + """ + + 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!') + else: + self.get_widgets()[widget.get_id()] = None + + def get_element_at_position(self, x, y): """Returns containing widget for character position @@ -848,8 +873,9 @@ def get_element_at_position(self, x, y): return self._popup elif self._popup is None: for widget_id in self.get_widgets().keys(): - if self.get_widgets()[widget_id]._contains_position(x, y): - return self.get_widgets()[widget_id] + if self.get_widgets()[widget_id] is not None: + if self.get_widgets()[widget_id]._contains_position(x, y): + return self.get_widgets()[widget_id] return None @@ -887,17 +913,16 @@ def _get_horizontal_neighbors(self, 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(): - if self.get_widgets()[widget_id]._is_row_col_inside(row, col) and widget_id not in id_list: - id_list.append(widget_id) + if self.get_widgets[widget_id] is not None: + if self.get_widgets()[widget_id]._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.info('Neighbors with ids {} for cell {},{} span {},{}'.format(id_list, - row_start, - col_start, - 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 @@ -935,17 +960,16 @@ def _get_vertical_neighbors(self, widget, direction): 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(): - if self.get_widgets()[widget_id]._is_row_col_inside(row, col) and widget_id not in id_list: - id_list.append(widget_id) + if self.get_widgets()[widget_id] is not None: + if self.get_widgets()[widget_id]._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.info('Neighbors with ids {} for cell {},{} span {},{}'.format(id_list, - row_start, - col_start, - 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 # CUI status functions. Used to switch between widgets, set the mode, and @@ -1009,10 +1033,10 @@ def set_selected_widget(self, widget_id): """ if widget_id in self.get_widgets().keys(): - self._logger.info('Setting selected widget to ID {}'.format(widget_id)) + self._logger.debug(f'Setting selected widget to ID {widget_id}') self._selected_widget = widget_id else: - self._logger.warn('Widget w/ ID {} does not exist among current widgets.'.format(widget_id)) + self._logger.warn(f'Widget w/ ID {widget_id} does not exist among current widgets.') def lose_focus(self): @@ -1040,12 +1064,13 @@ def move_focus(self, widget, auto_press_buttons=True): 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 widget.command is not None: widget.command() - self._logger.info('Moved focus to button {} - ran autofocus command'.format(widget.get_title())) + 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) @@ -1053,7 +1078,8 @@ def move_focus(self, widget, auto_press_buttons=True): widget.set_selected(True) self._in_focused_mode = True self.status_bar.set_text(widget.get_help_text()) - self._logger.info('Moved focus to widget {}'.format(widget.get_title())) + + self._logger.debug(f'Moved focus to widget {widget.get_title()}') def _cycle_widgets(self, reverse=False): @@ -1066,21 +1092,26 @@ def _cycle_widgets(self, reverse=False): """ num_widgets = len(self.get_widgets().keys()) - current_widget_num = int(self._selected_widget.split('Widget')[1]) + current_widget_num = self._selected_widget if not reverse: next_widget_num = current_widget_num + 1 - if next_widget_num == num_widgets: - next_widget_num = 0 + 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 next_widget_num < 0: - next_widget_num = num_widgets - 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 = 'Widget{}'.format(current_widget_num) - next_widget_id = 'Widget{}'.format(next_widget_num) + current_widget_id = current_widget_num + next_widget_id = next_widget_num + if self._in_focused_mode and cycle_key in self.get_widgets()[current_widget_id]._key_commands.keys(): # In the event that we are focusing on a widget with that key defined, we do not cycle. pass @@ -1114,9 +1145,9 @@ def show_message_popup(self, title, text): Message text """ - color = WHITE_ON_BLACK + color = WHITE_ON_BLACK self._popup = py_cui.popups.MessagePopup(self, title, text, color, self._renderer, self._logger) - self._logger.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title())) + self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') def show_warning_popup(self, title, text): @@ -1130,9 +1161,9 @@ def show_warning_popup(self, title, text): Warning text """ - color = YELLOW_ON_BLACK + color = YELLOW_ON_BLACK self._popup = py_cui.popups.MessagePopup(self, 'WARNING - ' + title, text, color, self._renderer, self._logger) - self._logger.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title())) + self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') def show_error_popup(self, title, text): @@ -1146,9 +1177,9 @@ def show_error_popup(self, title, text): Error text """ - color = RED_ON_BLACK + color = RED_ON_BLACK self._popup = py_cui.popups.MessagePopup(self, 'ERROR - ' + title, text, color, self._renderer, self._logger) - self._logger.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title())) + self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') def show_yes_no_popup(self, title, command): @@ -1164,9 +1195,9 @@ def show_yes_no_popup(self, title, command): A function taking in a single boolean parameter. Will be fired with True if yes selected, false otherwise """ - color = WHITE_ON_BLACK + 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.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title())) + self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') def show_text_box_popup(self, title, command, password=False): @@ -1184,9 +1215,9 @@ def show_text_box_popup(self, title, command, password=False): If true, write characters as '*' """ - color = WHITE_ON_BLACK + color = WHITE_ON_BLACK self._popup = py_cui.popups.TextBoxPopup(self, title, color, command, self._renderer, password, self._logger) - self._logger.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title())) + self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') def show_menu_popup(self, title, menu_items, command, run_command_if_none=False): @@ -1206,9 +1237,9 @@ def show_menu_popup(self, title, menu_items, command, run_command_if_none=False) If True, will run command passing in None if no menu item selected. """ - color = WHITE_ON_BLACK + 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.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title())) + self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') def show_loading_icon_popup(self, title, message, callback=None): @@ -1226,11 +1257,12 @@ def show_loading_icon_popup(self, title, message, callback=None): if callback is not None: self._post_loading_callback = callback + self._logger.debug(f'Post loading callback funciton set to {str(callback)}') - color = WHITE_ON_BLACK + color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingIconPopup(self, title, message, color, self._renderer, self._logger) - self._logger.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_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, num_items, callback=None): @@ -1250,11 +1282,12 @@ def show_loading_bar_popup(self, title, num_items, callback=None): if callback is not None: self._post_loading_callback = callback + self._logger.debug(f'Post loading callback funciton set to {str(callback)}') - color = WHITE_ON_BLACK + color = WHITE_ON_BLACK self._loading = True - self._popup = py_cui.popups.LoadingBarPopup(self, title, num_items, color, self._renderer, self._logger) - self._logger.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title())) + 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, fields, passwd_fields=[], required=[], callback=None): @@ -1276,9 +1309,21 @@ def show_form_popup(self, title, fields, passwd_fields=[], required=[], callback 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}') def show_filedialog_popup(self, popup_type='openfile', initial_dir='.', callback=None, ascii_icons=True, limit_extensions=[]): @@ -1300,7 +1345,17 @@ def show_filedialog_popup(self, popup_type='openfile', initial_dir='.', callback If not none, fired after loading is completed. Must be a no-arg function """ - 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 title {title}') def increment_loading_bar(self): @@ -1321,7 +1376,7 @@ def stop_loading_popup(self): self._loading = False self.close_popup() - self._logger.info('Stopping open loading popup') + self._logger.debug('Stopping open loading popup') def close_popup(self): @@ -1346,8 +1401,9 @@ def _refresh_height_width(self, height, width): self._height = height self._width = width self._grid.update_grid_height_width(self._height, self._width) - for widget_id in self._widgets.keys(): - self._widgets[widget_id].update_height_width() + for widget_id in self.get_widgets().keys(): + if self.get_widgets()[widget_id] is not None: + self.get_widgets()[widget_id].update_height_width() if self._popup is not None: self._popup.update_height_width() @@ -1369,9 +1425,10 @@ def _draw_widgets(self): """Function that draws all of the widgets to the screen """ - for widget_key in self.get_widgets().keys(): - if widget_key != self._selected_widget: - self.get_widgets()[widget_key]._draw() + for widget_id in self.get_widgets().keys(): + if widget_id != self._selected_widget: + if self.get_widgets()[widget_id] is not None: + self.get_widgets()[widget_id]._draw() # We draw the selected widget last to support cursor location. if self._selected_widget is not None: @@ -1418,11 +1475,11 @@ def _display_window_warning(self, stdscr, error_info): stdscr.clear() stdscr.attron(curses.color_pair(RED_ON_BLACK)) stdscr.addstr(0, 0, 'Error displaying CUI!!!') - stdscr.addstr(1, 0, 'Error Type: {}'.format(error_info)) + 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.info('Encountered error -> {}'.format(error_info)) + self._logger.error(f'Encountered error -> {error_info}') def _handle_key_presses(self, key_pressed): @@ -1447,10 +1504,10 @@ def _handle_key_presses(self, key_pressed): self.status_bar.set_text(self._init_status_bar_text) self._in_focused_mode = False selected_widget.set_selected(False) - self._logger.info('Exiting focus mode on widget {}'.format(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.info('Widget {} handling {} key'.format(selected_widget.get_title(), key_pressed)) + 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 @@ -1461,7 +1518,7 @@ def _handle_key_presses(self, key_pressed): for key in self._keybindings.keys(): if key_pressed == key: command = self._keybindings[key] - self._logger.info('Detected binding for key {}, running command {}'.format(key_pressed, 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. @@ -1470,11 +1527,11 @@ def _handle_key_presses(self, key_pressed): neighbor = self._check_if_neighbor_exists(key_pressed) if neighbor is not None: self.set_selected_widget(neighbor) - self._logger.info('Navigated to neighbor widget {}'.format(self.get_widgets()[self._selected_widget].get_title())) + self._logger.debug(f'Navigated to neighbor widget {self.get_widgets()[self._selected_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.info('Popup {} handling key {}'.format(self._popup.get_title(), key_pressed)) + self._logger.debug(f'Popup {self._popup.get_title()} handling key {key_pressed}') self._popup._handle_key_press(key_pressed) @@ -1538,7 +1595,7 @@ def _draw(self, stdscr): # This is what allows the CUI to be responsive. Adjust grid size based on current terminal size # Resize the grid and the widgets if there was a resize operation if key_pressed == curses.KEY_RESIZE: - self._logger.info('Resizing CUI to new dimensions {} by {}'.format(height, width)) + self._logger.debug(f'Resizing CUI to new dimensions {height} by {width}') try: self._refresh_height_width(height, width) except py_cui.errors.PyCUIOutOfBoundsError as e: @@ -1561,7 +1618,7 @@ def _draw(self, stdscr): # If we have a post_loading_callback, fire it here if self._post_loading_callback is not None and not self._loading: - self._logger.info('Firing post-loading callback function {}'.format(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 @@ -1612,7 +1669,7 @@ def _draw(self, stdscr): stdscr.refresh() curses.endwin() if self._on_stop is not None: - self._logger.info('Firing onstop function {}'.format(self._on_stop.__name__)) + self._logger.debug(f'Firing onstop function {self._on_stop.__name__}') self._on_stop() @@ -1626,6 +1683,7 @@ def __format__(self, fmt): """ out = '' - for widget in self.get_widgets().keys(): - out += '{}\n'.format(self.get_widgets()[widget].get_title()) + for _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' return out From c2c35264455f48d0ae6331d193a7fd6d210740ae Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sun, 16 May 2021 10:28:52 -0400 Subject: [PATCH 03/40] Add wheel to list of development dependencies --- requirements_dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 906f317..ecfb67a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,6 @@ pytest pytest-cov +wheel npdoc2md mkdocs -windows-curses ; platform_system=="Windows" \ No newline at end of file +windows-curses ; platform_system=="Windows" From 96c6cd0903e67235d48cde6cf7534ac8df6ead1a Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sun, 16 May 2021 10:30:36 -0400 Subject: [PATCH 04/40] Change live debugger into a UI element to simplify drawing logic --- examples/simple_todo_list.py | 3 +- py_cui/__init__.py | 37 ++++----- py_cui/debug.py | 141 +++++++++++++++++++++++++++++------ 3 files changed, 138 insertions(+), 43 deletions(-) diff --git a/examples/simple_todo_list.py b/examples/simple_todo_list.py index b6c2f51..533aba4 100644 --- a/examples/simple_todo_list.py +++ b/examples/simple_todo_list.py @@ -140,6 +140,7 @@ def save_todo_file(self): # Create the CUI, pass it to the wrapper object, and start it root = py_cui.PyCUI(8, 6) root.set_title('CUI TODO List') -#root.enable_logging(logging_level=logging.DEBUG) +root.enable_logging(logging_level=logging.DEBUG) +#root.toggle_live_debug_mode() s = SimpleTodoList(root) root.start() diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 1f828dd..1ab5d37 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -174,6 +174,7 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, 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 = None # Callback to fire when CUI is stopped. self._on_stop = None @@ -219,6 +220,9 @@ def set_widget_cycle_key(self, forward_cycle_key=None, reverse_cycle_key=None): self._reverse_cycle_key = reverse_cycle_key + def set_toggle_live_debug_key(self, toggle_debug_key): + self._toggle_live_debug_key = toggle_debug_key + def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging.DEBUG): """Function enables logging for py_cui library @@ -233,22 +237,11 @@ def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging try: 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 = py_cui.keys.KEY_CTRL_D except PermissionError as e: print('Failed to initialize logger: {}'.format(str(e))) - def is_live_debug_mode_active(self): - if self._logger is None: - return False - else: - return self._logger.is_live_debug_enabled() - - - def toggle_live_debug_mode(self): - if self._logger is not None: - self._logger.toggle_live_debug() - - def apply_widget_set(self, new_widget_set): """Function that replaces all widgets in a py_cui with those of a different widget set @@ -383,7 +376,6 @@ def _initialize_colors(self): # Start colors in curses. # For each color pair in color map, initialize color combination. curses.start_color() - curses.init_color(curses.COLOR_BLUE, 0, 0, 500) for color_pair in py_cui.colors._COLOR_MAP.keys(): fg_color, bg_color = py_cui.colors._COLOR_MAP[color_pair] curses.init_pair(color_pair, fg_color, bg_color) @@ -399,6 +391,8 @@ def _initialize_widget_renderer(self): self._widgets[widget_id]._assign_renderer(self._renderer) 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) def toggle_unicode_borders(self): @@ -1381,6 +1375,9 @@ def _draw_widgets(self): if self._selected_widget is not None: self.get_widgets()[self._selected_widget]._draw() + if self._logger is not None and self._logger.is_live_debug_enabled(): + self._logger.draw_live_debug() + self._logger.info('Drew widgets') @@ -1446,13 +1443,17 @@ def _handle_key_presses(self, key_pressed): # If logging is enabled, the Ctrl + D key code will enable "live-debug" # mode, where debug messages are printed on the screen - if self._logger is not None: - if key_pressed == py_cui.keys.KEY_CTRL_D: - self.toggle_live_debug_mode() + if self._logger is not None and self._toggle_live_debug_key is not 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 self._logger is not None and self._logger.is_live_debug_enabled(): + self._logger._live_debug_element._handle_key_press(key_pressed) # If we are in focus mode, the widget has all of the control of the keyboard except # for the escape key, which exits focus mode. - if self._in_focused_mode and self._popup is None: + elif self._in_focused_mode and self._popup is None: if key_pressed == py_cui.keys.KEY_ESCAPE: self.status_bar.set_text(self._init_status_bar_text) self._in_focused_mode = False @@ -1593,7 +1594,7 @@ def _draw(self, stdscr): self._popup._draw() # If we are in live debug mode, we draw our debug messages - if self.is_live_debug_mode_active(): + if self._logger.is_live_debug_enabled(): self._logger.draw_live_debug() except curses.error as e: diff --git a/py_cui/debug.py b/py_cui/debug.py index e51b67e..794e1e6 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -77,6 +77,115 @@ def _initialize_logger(py_cui_root, name=None, custom_logger=True): logging._releaseLock() +class LiveDebugImplementation(py_cui.ui.MenuImplementation): + + def __init__(self, parent_logger): + + super().__init__(parent_logger) + self._live_debug_level = logging.ERROR + self._live_debug_enabled = False + self._buffer_size = 100 + self._num_view_msg = 10 + #self._live_debug_alignment = 'TOP' + self._current_bottom_debug_msg = 0 + + def add_item(self, item, viewport_height): + if len(self._view_items) == self._buffer_size: + self._view_items.pop(0) + self._view_items.append(str(item)) + #self._top_view = len(self._view_items) - viewport_height + + +class LiveDebugElement(py_cui.ui.UIElement, LiveDebugImplementation): + + + def __init__(self, parent_logger): + LiveDebugImplementation.__init__(self, parent_logger) + py_cui.ui.UIElement.__init__(self, 'LiveDebug', 'PyCUI Live Debug', None, parent_logger) + + + def print_to_live_debug_buffer(self, msg, msg_type): + + try: + viewport_height = self.get_viewport_height() + self.add_item('{} - {}'.format(msg_type, msg), viewport_height) + except AttributeError: + self.add_item('{} - {}'.format(msg_type, msg), 0) + + + def get_absolute_start_pos(self): + return 5, 5 + + + def get_absolute_stop_pos(self): + stop_x = 10 + stop_y = 10 + if self._logger.py_cui_root is not None: + stop_x = 6 * int(self._logger.py_cui_root._width / 7) - 2 + stop_y = 6 * int(self._logger.py_cui_root._height / 7) - 2 + return stop_x, stop_y + + def _handle_mouse_press(self, x, y): + """Override of base class function, handles mouse press in menu + + Parameters + ---------- + x, y : int + Coordinates of mouse press + """ + + super()._handle_mouse_press(x, y) + viewport_top = self._start_y + self._pady + 1 + 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) + + + def _handle_key_press(self, key_pressed): + + if key_pressed == py_cui.keys.KEY_ESCAPE: + self._logger.toggle_live_debug() + + 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: + self._scroll_down(viewport_height) + if key_pressed == py_cui.keys.KEY_HOME: + self._jump_to_top() + if key_pressed == py_cui.keys.KEY_END: + self._jump_to_bottom(viewport_height) + if key_pressed == py_cui.keys.KEY_PAGE_UP: + self._jump_up() + if key_pressed == py_cui.keys.KEY_PAGE_DOWN: + self._jump_down(viewport_height) + + + def _draw(self): + """Overrides base class draw function + """ + + self._renderer.set_color_mode(py_cui.WHITE_ON_BLACK) + self._renderer.draw_border(self) + counter = self._pady + 1 + line_counter = 0 + for item in self._view_items: + line = str(item) + if line_counter < self._top_view: + line_counter = line_counter + 1 + else: + if counter >= self._height - self._pady - 1: + break + if line_counter == self._selected_item: + self._renderer.draw_text(self, line, self._start_y + counter, selected=True) + else: + self._renderer.draw_text(self, line, self._start_y + counter) + counter = counter + 1 + line_counter = line_counter + 1 + self._renderer.unset_color_mode(py_cui.WHITE_ON_BLACK) + self._renderer.reset_cursor(self) + + class PyCUILogger(logging.Logger): """Custom logger class for py_cui, extends the base logging.Logger Class @@ -100,11 +209,8 @@ def __init__(self, name): super(PyCUILogger, self).__init__(name) self._live_debug_level = logging.ERROR self._live_debug_enabled = False - self._debug_msg_buffer = [] - self._buffer_size = 100 - self._num_view_msg = 10 - self._live_debug_alignment = 'TOP' - self._current_bottom_debug_msg = 0 + self._live_debug_element = LiveDebugElement(self) + self.py_cui_root = None def set_live_debug_alignment(self, alignment = 'TOP'): @@ -117,16 +223,9 @@ def is_live_debug_enabled(self): def toggle_live_debug(self): self._live_debug_enabled = not self._live_debug_enabled - def draw_live_debug(self): - num_messages_to_display = self._num_view_msg - if self.py_cui_root._height < num_messages_to_display: - num_messages_to_display = self.py_cui_root._height - 1 - if len(self._debug_msg_buffer) < num_messages_to_display: - num_messages_to_display = len(self._debug_msg_buffer) - 1 - - for i in range(0, num_messages_to_display): - self.py_cui_root._stdscr.addstr(num_messages_to_display - i + 1, 1, self._debug_msg_buffer[self._current_bottom_debug_msg - i]) + if self.is_live_debug_enabled() and self.py_cui_root is not None: + self._live_debug_element._draw() def _assign_root_window(self, py_cui_root): """Attaches logger to the root window for live debugging @@ -147,12 +246,6 @@ def _get_debug_text(self, text): func.co_name, os.path.basename(func.co_filename), func.co_firstlineno) - - - def _add_msg_to_buffer(self, msg): - if len(self._debug_msg_buffer) == self._buffer_size: - self._debug_msg_buffer.pop(0) - self._debug_msg_buffer.append(msg) def info(self, text): @@ -165,7 +258,7 @@ def info(self, text): """ debug_text = self._get_debug_text(text) - self._add_msg_to_buffer(debug_text) + self._live_debug_element.print_to_live_debug_buffer(debug_text, 'INFO') super().info(debug_text) @@ -180,7 +273,7 @@ def debug(self, text): debug_text = self._get_debug_text(text) if self._live_debug_level == logging.DEBUG and self._live_debug_enabled: - self._add_msg_to_buffer(debug_text) + self._live_debug_element.print_to_live_debug_buffer(debug_text, 'DEBUG') super().debug(debug_text) @@ -195,7 +288,7 @@ def warn(self, text): debug_text = self._get_debug_text(text) if self._live_debug_level < logging.WARN and self._live_debug_enabled: - self._add_msg_to_buffer(debug_text) + self._live_debug_element.print_to_live_debug_buffer(debug_text, 'WARN') super().warn(debug_text) @@ -210,5 +303,5 @@ def error(self, text): debug_text = self._get_debug_text(text) if self._live_debug_level < logging.ERROR and self._live_debug_enabled: - self._add_msg_to_buffer(debug_text) + self._live_debug_element.print_to_live_debug_buffer(debug_text, 'ERROR') super().error(debug_text) From cb330270355451929db96ac99d57b62cadc0f1b3 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sun, 16 May 2021 11:36:15 -0400 Subject: [PATCH 05/40] Live debug mode now working as intended --- examples/simple_todo_list.py | 3 ++- py_cui/__init__.py | 3 ++- py_cui/debug.py | 42 +++++++++++++++++++----------------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/examples/simple_todo_list.py b/examples/simple_todo_list.py index 533aba4..0c3c390 100644 --- a/examples/simple_todo_list.py +++ b/examples/simple_todo_list.py @@ -140,7 +140,8 @@ def save_todo_file(self): # Create the CUI, pass it to the wrapper object, and start it root = py_cui.PyCUI(8, 6) root.set_title('CUI TODO List') -root.enable_logging(logging_level=logging.DEBUG) +#root.enable_logging(logging_level=logging.DEBUG) +root.enable_logging(logging_level=logging.ERROR) #root.toggle_live_debug_mode() s = SimpleTodoList(root) root.start() diff --git a/py_cui/__init__.py b/py_cui/__init__.py index bec7ac5..391135f 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1356,7 +1356,8 @@ def _refresh_height_width(self, height, width): self._widgets[widget_id].update_height_width() 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() def get_absolute_size(self): """Returns dimensions of CUI diff --git a/py_cui/debug.py b/py_cui/debug.py index 794e1e6..8cb5a38 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -8,6 +8,7 @@ import logging import inspect import py_cui +import datetime def _enable_logging(logger, replace_log_file=True, filename='py_cui_log.txt', logging_level=logging.DEBUG): @@ -89,11 +90,10 @@ def __init__(self, parent_logger): #self._live_debug_alignment = 'TOP' self._current_bottom_debug_msg = 0 - def add_item(self, item, viewport_height): + def add_item(self, item): if len(self._view_items) == self._buffer_size: self._view_items.pop(0) self._view_items.append(str(item)) - #self._top_view = len(self._view_items) - viewport_height class LiveDebugElement(py_cui.ui.UIElement, LiveDebugImplementation): @@ -102,29 +102,29 @@ class LiveDebugElement(py_cui.ui.UIElement, LiveDebugImplementation): def __init__(self, parent_logger): LiveDebugImplementation.__init__(self, parent_logger) py_cui.ui.UIElement.__init__(self, 'LiveDebug', 'PyCUI Live Debug', None, parent_logger) + self._start_x = 5 + self._start_y = 5 + self._stop_x = 150 + self._stop_y = 25 def print_to_live_debug_buffer(self, msg, msg_type): - try: - viewport_height = self.get_viewport_height() - self.add_item('{} - {}'.format(msg_type, msg), viewport_height) - except AttributeError: - self.add_item('{} - {}'.format(msg_type, msg), 0) + self.add_item(f'{datetime.datetime.now()} - {msg_type} | {msg}') def get_absolute_start_pos(self): - return 5, 5 + start_x = int(self._logger.py_cui_root._width / 7) + 2 + start_y = int(self._logger.py_cui_root._height / 7) + 2 + return start_x, start_y def get_absolute_stop_pos(self): - stop_x = 10 - stop_y = 10 - if self._logger.py_cui_root is not None: - stop_x = 6 * int(self._logger.py_cui_root._width / 7) - 2 - stop_y = 6 * int(self._logger.py_cui_root._height / 7) - 2 + stop_x = 6 * int(self._logger.py_cui_root._width / 7) - 2 + stop_y = 6 * int(self._logger.py_cui_root._height / 7) - 2 return stop_x, stop_y + def _handle_mouse_press(self, x, y): """Override of base class function, handles mouse press in menu @@ -169,7 +169,7 @@ def _draw(self): self._renderer.draw_border(self) counter = self._pady + 1 line_counter = 0 - for item in self._view_items: + for item in reversed(self._view_items): line = str(item) if line_counter < self._top_view: line_counter = line_counter + 1 @@ -209,8 +209,8 @@ def __init__(self, name): super(PyCUILogger, self).__init__(name) self._live_debug_level = logging.ERROR self._live_debug_enabled = False - self._live_debug_element = LiveDebugElement(self) - self.py_cui_root = None + self.py_cui_root = None + self._live_debug_element = LiveDebugElement(self) def set_live_debug_alignment(self, alignment = 'TOP'): @@ -235,6 +235,7 @@ def _assign_root_window(self, py_cui_root): raise TypeError('py_cui_root type must be py_cui.PyCUI') self.py_cui_root = py_cui_root + self._live_debug_element.update_height_width() def _get_debug_text(self, text): @@ -258,7 +259,8 @@ def info(self, text): """ debug_text = self._get_debug_text(text) - self._live_debug_element.print_to_live_debug_buffer(debug_text, 'INFO') + if self._live_debug_level <= logging.INFO and self._live_debug_enabled: + self._live_debug_element.print_to_live_debug_buffer(debug_text, 'INFO') super().info(debug_text) @@ -272,7 +274,7 @@ def debug(self, text): """ debug_text = self._get_debug_text(text) - if self._live_debug_level == logging.DEBUG and self._live_debug_enabled: + if self._live_debug_level <= logging.DEBUG and self._live_debug_enabled: self._live_debug_element.print_to_live_debug_buffer(debug_text, 'DEBUG') super().debug(debug_text) @@ -287,7 +289,7 @@ def warn(self, text): """ debug_text = self._get_debug_text(text) - if self._live_debug_level < logging.WARN and self._live_debug_enabled: + if self._live_debug_level <= logging.WARN and self._live_debug_enabled: self._live_debug_element.print_to_live_debug_buffer(debug_text, 'WARN') super().warn(debug_text) @@ -302,6 +304,6 @@ def error(self, text): """ debug_text = self._get_debug_text(text) - if self._live_debug_level < logging.ERROR and self._live_debug_enabled: + if self._live_debug_level <= logging.ERROR and self._live_debug_enabled: self._live_debug_element.print_to_live_debug_buffer(debug_text, 'ERROR') super().error(debug_text) From e3e476e1956d61912ec2703ac5980b0b9a45aa51 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sun, 16 May 2021 12:11:18 -0400 Subject: [PATCH 06/40] Docstring updates for debug classes and functions --- py_cui/debug.py | 115 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 24 deletions(-) diff --git a/py_cui/debug.py b/py_cui/debug.py index 8cb5a38..7ab99e8 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -11,7 +11,7 @@ import datetime -def _enable_logging(logger, replace_log_file=True, filename='py_cui_log.txt', logging_level=logging.DEBUG): +def _enable_logging(logger, replace_log_file=True, filename='py_cui.log', logging_level=logging.DEBUG): """Function that creates basic logging configuration for selected logger Parameters @@ -31,16 +31,19 @@ def _enable_logging(logger, replace_log_file=True, filename='py_cui_log.txt', lo Only the custom PyCUILogger can be used here. """ + # Remove existing log file if necessary abs_path = os.path.abspath(filename) if replace_log_file and os.path.exists(abs_path): os.remove(abs_path) + # Permission check and check if we are using custom py_cui logger if not os.access(os.path.dirname(abs_path), os.W_OK): raise PermissionError('You do not have permission to create py_cui.log file.') if not isinstance(logger, PyCUILogger): raise TypeError('Only the PyCUILogger can be used for logging in the py_cui module.') + # Create our logging utility objects log_file = logging.FileHandler(filename) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s | %(message)s') log_file.setFormatter(formatter) @@ -79,47 +82,85 @@ def _initialize_logger(py_cui_root, name=None, custom_logger=True): class LiveDebugImplementation(py_cui.ui.MenuImplementation): + """Implementation class for the live debug menu - builds off of the scroll menu implementation + + Attributes + ---------- + _live_debug_level : int + Debug level at which to display messages. Can be separate from the default logging level + _buffer_size : List[str] + Number of log messages to keep buffered in the live debug window + """ + def __init__(self, parent_logger): + """Initializer for LiveDebugImplementation + """ super().__init__(parent_logger) self._live_debug_level = logging.ERROR - self._live_debug_enabled = False + # Make default buffer size 100 self._buffer_size = 100 - self._num_view_msg = 10 - #self._live_debug_alignment = 'TOP' - self._current_bottom_debug_msg = 0 - def add_item(self, item): + + def add_item(self, msg, log_level): + """Override of default MenuImplementation add_item function + + If items override the buffer pop the oldest log message + + Parameters + ---------- + item : str + Log message to add + """ + if len(self._view_items) == self._buffer_size: self._view_items.pop(0) - self._view_items.append(str(item)) + self._view_items.append(f'{datetime.datetime.now()} - {log_leve} | {msg}') class LiveDebugElement(py_cui.ui.UIElement, LiveDebugImplementation): - + """UIElement class for the live debug utility. extends from base UIElement class and LiveDebugImplementation + """ def __init__(self, parent_logger): + """Initializer for LiveDebugElement class + """ + LiveDebugImplementation.__init__(self, parent_logger) py_cui.ui.UIElement.__init__(self, 'LiveDebug', 'PyCUI Live Debug', None, parent_logger) + + # Initialize these to some dummy values to start with - will get overriden once + # parent logger is assigned a root py_cui window self._start_x = 5 self._start_y = 5 self._stop_x = 150 self._stop_y = 25 - def print_to_live_debug_buffer(self, msg, msg_type): - - self.add_item(f'{datetime.datetime.now()} - {msg_type} | {msg}') + def get_absolute_start_pos(self): + """Override of base UI element class function. Sets start position relative to entire UI size + Returns + ------- + start_x, start_y : int, int + Start position x, y coords in terminal characters + """ - def get_absolute_start_pos(self): start_x = int(self._logger.py_cui_root._width / 7) + 2 start_y = int(self._logger.py_cui_root._height / 7) + 2 return start_x, start_y def get_absolute_stop_pos(self): + """Override of base UI element class function. Sets stop position relative to entire UI size + + Returns + ------- + stop_x, stop_y : int, int + Stop position x, y coords in terminal characters + """ + stop_x = 6 * int(self._logger.py_cui_root._width / 7) - 2 stop_y = 6 * int(self._logger.py_cui_root._height / 7) - 2 return stop_x, stop_y @@ -142,7 +183,18 @@ def _handle_mouse_press(self, x, y): def _handle_key_press(self, key_pressed): + """Override of base class function. + Essentially the same as the ScrollMenu widget _handle_key_press, with the exception that Esc breaks + out of live debug mode. + + Parameters + ---------- + key_pressed : int + The keycode of the pressed key + """ + + # If we have escape pressed, we break out of live debug mode if key_pressed == py_cui.keys.KEY_ESCAPE: self._logger.toggle_live_debug() @@ -162,7 +214,7 @@ def _handle_key_press(self, key_pressed): def _draw(self): - """Overrides base class draw function + """Overrides base class draw function. Mostly a copy of ScrollMenu widget - but reverse item list """ self._renderer.set_color_mode(py_cui.WHITE_ON_BLACK) @@ -212,11 +264,7 @@ def __init__(self, name): self.py_cui_root = None self._live_debug_element = LiveDebugElement(self) - - def set_live_debug_alignment(self, alignment = 'TOP'): - self._live_debug_alignment = alignment - - + def is_live_debug_enabled(self): return self._live_debug_enabled @@ -224,14 +272,23 @@ def toggle_live_debug(self): self._live_debug_enabled = not self._live_debug_enabled def draw_live_debug(self): + """Function that draws the live debug UI element if applicable + """ + if self.is_live_debug_enabled() and self.py_cui_root is not None: self._live_debug_element._draw() + def _assign_root_window(self, py_cui_root): - """Attaches logger to the root window for live debugging + """Function that assigns a PyCUI root object to the logger. Important for live-debug hooks + + Parameters + ---------- + py_cui_root : PyCUI + Root PyCUI object for the application """ - if not isinstance(py_cui_root, py_cui.PyCUI): + if not isinstance(py_cui_root, py_cui.PyCUI): raise TypeError('py_cui_root type must be py_cui.PyCUI') self.py_cui_root = py_cui_root @@ -240,6 +297,16 @@ def _assign_root_window(self, py_cui_root): def _get_debug_text(self, text): """Function that generates full debug text for the log + + Parameters + ---------- + text : str + Log message + + Returns + ------- + msg : str + Log message with function, file, and line num info """ func = inspect.currentframe().f_back.f_back.f_code @@ -250,7 +317,7 @@ def _get_debug_text(self, text): def info(self, text): - """Adds stacktrace info to log + """Override of base logger info function to add hooks for live debug mode Parameters ---------- @@ -265,7 +332,7 @@ def info(self, text): def debug(self, text): - """Function that allows for live debugging of py_cui programs by displaying log messages in the satus bar + """Override of base logger debug function to add hooks for live debug mode Parameters ---------- @@ -280,7 +347,7 @@ def debug(self, text): def warn(self, text): - """Function that allows for live debugging of py_cui programs by displaying log messages in the satus bar + """Override of base logger warn function to add hooks for live debug mode Parameters ---------- @@ -295,7 +362,7 @@ def warn(self, text): def error(self, text): - """Function that displays error messages live in status bar for py_cui logging + """Override of base logger error function to add hooks for live debug mode Parameters ---------- From e96697dcc55fb7ace2512cf210db17c672635450 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sun, 16 May 2021 20:45:15 -0400 Subject: [PATCH 07/40] Begin migration to only python 3.6 support --- py_cui/__init__.py | 30 +++++++++++++++--------------- py_cui/colors.py | 2 +- py_cui/debug.py | 2 +- py_cui/grid.py | 3 ++- py_cui/popups.py | 13 +++++-------- py_cui/renderer.py | 34 ++++++++++++++++------------------ py_cui/ui.py | 34 +++++++++++++++++----------------- py_cui/widget_set.py | 31 +++++++++++++++---------------- 8 files changed, 72 insertions(+), 77 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 4fdf3f7..de33e5f 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -518,7 +518,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= A reference to the created checkbox object. """ - id = len(self._widgets.keys()) + id = len(self.get_widgets().keys()) new_checkbox_menu = py_cui.widgets.CheckBoxMenu(id, title, self._grid, @@ -531,7 +531,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= self._logger, checked_char) new_checkbox_menu._assign_renderer(self._renderer) - self._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))}') @@ -568,7 +568,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = A reference to the created textbox object. """ - id = len(self._widgets.keys()) + id = len(self.get_widgets().keys()) new_text_box = py_cui.widgets.TextBox(id, title, self._grid, @@ -580,7 +580,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = initial_text, password) new_text_box._assign_renderer(self._renderer) - self._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))}') @@ -615,7 +615,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx A reference to the created textblock object. """ - id = len(self._widgets.keys()) + id = len(self.get_widgets().keys()) new_text_block = py_cui.widgets.ScrollTextBlock(id, title, self._grid, @@ -628,7 +628,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx self._logger, initial_text) new_text_block._assign_renderer(self._renderer) - self._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))}') @@ -661,7 +661,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, A reference to the created label object. """ - id = len(self._widgets.keys()) + id = len(self.get_widgets().keys()) new_label = py_cui.widgets.Label(id, title, self._grid, @@ -673,7 +673,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady, self._logger) new_label._assign_renderer(self._renderer) - self._widgets[id] = 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 @@ -706,7 +706,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad A reference to the created block label object. """ - id = len(self._widgets.keys()) + id = len(self.get_widgets().keys()) new_label = py_cui.widgets.BlockLabel(id, title, self._grid, @@ -719,7 +719,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad center, self._logger) new_label._assign_renderer(self._renderer) - self._widgets[id] = 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 @@ -752,7 +752,7 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 A reference to the created button object. """ - id = len(self._widgets.keys()) + id = len(self.get_widgets().keys()) new_button = py_cui.widgets.Button(id, title, self._grid, @@ -765,7 +765,7 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 self._logger, command) new_button._assign_renderer(self._renderer) - self._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))}') @@ -808,7 +808,7 @@ def add_slider(self, title, row, column, row_span=1, A reference to the created slider object. """ - id = len(self._widgets.keys()) + id = len(self.get_widgets().keys()) new_slider = py_cui.controls.slider.SliderWidget(id, title, self._grid, @@ -824,7 +824,7 @@ def add_slider(self, title, row, column, row_span=1, step, init_val) new_slider._assign_renderer(self._renderer) - self._widgets[id] = new_slider + 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 @@ -913,7 +913,7 @@ def _get_horizontal_neighbors(self, 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(): - if self.get_widgets[widget_id] is not None: + if self.get_widgets()[widget_id] is not None: if self.get_widgets()[widget_id]._is_row_col_inside(row, col) and widget_id not in id_list: id_list.append(widget_id) diff --git a/py_cui/colors.py b/py_cui/colors.py index 0cf55e2..02e09ce 100644 --- a/py_cui/colors.py +++ b/py_cui/colors.py @@ -360,6 +360,6 @@ def generate_fragments(self, widget, line, render_text, selected=False): elif self._match_type == 'region': fragments = self._split_text_on_region(widget, render_text, selected) - self._logger.info('Generated fragments: {}'.format(fragments)) + self._logger.debug(f'Generated fragments: {fragments}') return fragments, match \ No newline at end of file diff --git a/py_cui/debug.py b/py_cui/debug.py index 2b04f8b..03c86f5 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -117,7 +117,7 @@ def _get_debug_text(self, text): """ func = inspect.currentframe().f_back.f_back.f_code - return "{}: Function {} in {}:{}".format(text, func.co_name, os.path.basename(func.co_filename), func.co_firstlineno) + return f'{text}: Function {func.co_name} in {os.path.basename(func.co_filename)}:{func.co_firstlineno}' def info(self, text): diff --git a/py_cui/grid.py b/py_cui/grid.py index a3089d4..cb3e5a0 100644 --- a/py_cui/grid.py +++ b/py_cui/grid.py @@ -184,4 +184,5 @@ def update_grid_height_width(self, height, width): self._column_width = int(self._width / self._num_columns) self._offset_x = self._width % self._num_columns self._offset_y = self._height % self._num_rows - self._logger.info('Updated grid. Cell dims: {}x{}, Offsets {},{}'.format(self._row_height, self._column_width, self._offset_x, self._offset_y)) \ No newline at end of file + self._logger.debug(f'Updated grid. Cell dims: {self._row_height}x{self._column_width}, \ + Offsets {self._offset_x},{self._offset_y}') \ No newline at end of file diff --git a/py_cui/popups.py b/py_cui/popups.py index c2954de..2e0fde2 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -404,7 +404,7 @@ def __init__(self, root, title, message, color, renderer, logger): """Initializer for LoadingIconPopup """ - super().__init__(root, title, '{} ... \\'.format(message), color, renderer, logger) + super().__init__(root, title, f'{message} ... \\', color, renderer, logger) self._loading_icons = ['\\', '|', '/', '-'] self._icon_counter = 0 self._message = message @@ -428,7 +428,7 @@ def _draw(self): """Overrides base draw function """ - self._text = '{} ... {}'.format(self._message, self._loading_icons[self._icon_counter]) + self._text = f'{self._message} ... {self._loading_icons[self._icon_counter]}' self._icon_counter = self._icon_counter + 1 if self._icon_counter == len(self._loading_icons): self._icon_counter = 0 @@ -454,7 +454,7 @@ def __init__(self, root, title, num_items, color, renderer, logger): """Initializer for LoadingBarPopup """ - super().__init__(root, title, '{} (0/{})'.format('-' * num_items, num_items), color, renderer, logger) + super().__init__(root, title, f'{"-" * num_items} (0/{num_items})', color, renderer, logger) self._num_items = num_items self._loading_icons = ['\\', '|', '/', '-'] self._icon_counter = 0 @@ -503,11 +503,8 @@ def _draw(self): if self._icon_counter == len(self._loading_icons): self._icon_counter = 0 - self.set_text('{}{} ({}/{}) {}'.format( '#' * completed_blocks, - '-' * non_completed_blocks, - self._completed_items, - self._num_items, - self._loading_icons[self._icon_counter])) + 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/renderer.py b/py_cui/renderer.py index 370f570..6e19538 100644 --- a/py_cui/renderer.py +++ b/py_cui/renderer.py @@ -212,16 +212,16 @@ def _draw_border_top(self, ui_element, y, with_title): title = ui_element.get_title() if not with_title or (len(title) + 4 >= width - 2 * padx): - render_text = '{}{}{}'.format( self._border_characters['UP_LEFT'], - self._border_characters['HORIZONTAL'] * (width - 2 - 2 * padx), - self._border_characters['UP_RIGHT']) + render_text = f'{self._border_characters["UP_LEFT"]}' \ + f'{self._border_characters["HORIZONTAL"] * (width - 2 - 2 * padx)}' \ + f'{self._border_characters["UP_RIGHT"]}' + self._stdscr.addstr(y, start_x + padx, render_text) else: - render_text = '{}{} {} {}{}'.format(self._border_characters['UP_LEFT'], - 2 * self._border_characters['HORIZONTAL'], - title, - self._border_characters['HORIZONTAL'] * (width - 6 - 2 * padx - len(title)), - self._border_characters['UP_RIGHT']) + render_text = f'{self._border_characters["UP_LEFT"]}{2 * self._border_characters["HORIZONTAL"]}' \ + f'{title} {self._border_characters["HORIZONTAL"] * (width - 6 - 2 * padx - len(title))}' \ + f'{self._border_characters["UP_RIGHT"]}' + self._stdscr.addstr(y, start_x + padx, render_text) @@ -240,9 +240,9 @@ def _draw_border_bottom(self, ui_element, y): start_x, _ = ui_element.get_start_position() _, width = ui_element.get_absolute_dimensions() - render_text = '{}{}{}'.format( self._border_characters['DOWN_LEFT'], - self._border_characters['HORIZONTAL'] * (width - 2 - 2 * padx), - self._border_characters['DOWN_RIGHT']) + render_text = f'{self._border_characters["DOWN_LEFT"]}' \ + f'{self._border_characters["HORIZONTAL"] * (width - 2 - 2 * padx)}' \ + f'{self._border_characters["DOWN_RIGHT"]}' self._stdscr.addstr(y, start_x + padx, render_text) @@ -261,9 +261,9 @@ def _draw_blank_row(self, ui_element, y): start_x, _ = ui_element.get_start_position() _, width = ui_element.get_absolute_dimensions() - render_text = '{}{}{}'.format( self._border_characters['VERTICAL'], - ' ' * (width - 2 - 2 * padx), - self._border_characters['VERTICAL']) + render_text = f'{self._border_characters["VERTICAL"]}' \ + f'{" " * (width - 2 - 2 * padx)}' \ + f'{self._border_characters["VERTICAL"]}' self._stdscr.addstr(y, start_x + padx, render_text) @@ -300,11 +300,9 @@ def _get_render_text(self, ui_element, line, centered, bordered, selected, start if len(line) - start_pos < render_text_length: if centered: - render_text = '{}'.format( line[start_pos:].center(render_text_length, - ' ')) + render_text = f'{line[start_pos:].center(render_text_length, " ")}' else: - render_text = '{}{}'.format(line[start_pos:], - ' ' * (render_text_length - len(line[start_pos:]))) + render_text = f'{line[start_pos:]}{" " * (render_text_length - len(line[start_pos:]))}' else: render_text = line[start_pos:start_pos + render_text_length] diff --git a/py_cui/ui.py b/py_cui/ui.py index b63b5a7..4a0ddeb 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -740,7 +740,7 @@ def _scroll_up(self): if self._selected_item > 0: self._selected_item = self._selected_item - 1 - self._logger.info('Scrolling up to item {}'.format(self._selected_item)) + self._logger.debug(f'Scrolling up to item {self._selected_item}') def _scroll_down(self, viewport_height): @@ -759,7 +759,7 @@ def _scroll_down(self, viewport_height): if self._selected_item > self._top_view + viewport_height: self._top_view = self._top_view + 1 - self._logger.info('Scrolling down to item {}'.format(self._selected_item)) + self._logger.debug(f'Scrolling down to item {self._selected_item}') def _jump_up(self): @@ -815,7 +815,7 @@ def add_item(self, item): Object to add to the menu. Must have implemented __str__ function """ - self._logger.info('Adding item {} to menu'.format(str(item))) + self._logger.debug(f'Adding item {str(item)} to menu') self._view_items.append(item) @@ -828,7 +828,7 @@ def add_item_list(self, item_list): list of objects to add as items to the scrollmenu """ - self._logger.info('Adding item list {} to menu'.format(str(item_list))) + self._logger.debug(f'Adding item list {str(item_list)} to menu') for item in item_list: self.add_item(item) @@ -839,7 +839,7 @@ def remove_selected_item(self): if len(self._view_items) == 0: return - self._logger.info('Removing {}'.format(str(self._view_items[self._selected_item]))) + self._logger.debug(f'Removing {str(self._view_items[self._selected_item])}') del self._view_items[self._selected_item] if self._selected_item >= len(self._view_items) and self._selected_item > 0: self._selected_item = self._selected_item - 1 @@ -856,7 +856,7 @@ def remove_item(self, item): if len(self._view_items) == 0 or item not in self._view_items: return - self._logger.info('Removing {}'.format(str(item))) + self._logger.debug(f'Removing {str(item)}') i_index = self._view_items.index(item) del self._view_items[i_index] if self._selected_item >= i_index: @@ -1103,7 +1103,7 @@ def get(self): text = '' for line in self._text_lines: - text = '{}{}\n'.format(text, line) + text = f'{text}{line}\n' return text @@ -1195,7 +1195,7 @@ def _move_left(self): self._viewport_x_start = self._viewport_x_start - 1 self._cursor_text_pos_x = self._cursor_text_pos_x - 1 - self._logger.info('Moved cursor left to pos {}'.format(self._cursor_text_pos_x)) + self._logger.debug(f'Moved cursor left to pos {self._cursor_text_pos_x}) def _move_right(self): @@ -1211,7 +1211,7 @@ def _move_right(self): self._viewport_x_start = self._viewport_x_start + 1 self._cursor_text_pos_x = self._cursor_text_pos_x + 1 - self._logger.info('Moved cursor right to pos {}'.format(self._cursor_text_pos_x)) + self._logger.debug(f'Moved cursor right to pos {self._cursor_text_pos_x}') def _move_up(self): @@ -1230,7 +1230,7 @@ def _move_up(self): self._cursor_x = self._cursor_x - (self._cursor_text_pos_x - temp) self._cursor_text_pos_x = temp - self._logger.info('Moved cursor up to line {}'.format(self._cursor_text_pos_y)) + self._logger.debug(f'Moved cursor up to line {self._cursor_text_pos_y}') def _move_down(self): @@ -1248,7 +1248,7 @@ def _move_down(self): self._cursor_x = self._cursor_x - (self._cursor_text_pos_x - temp) self._cursor_text_pos_x = temp - self._logger.info('Moved cursor down to line {}'.format(self._cursor_text_pos_y)) + self._logger.debug(f'Moved cursor down to line {self._cursor_text_pos_y}') @@ -1257,7 +1257,7 @@ def _handle_newline(self): """ current_line = self.get_current_line() - self._logger.info('Inserting newline in location {}'.format(self._cursor_text_pos_x)) + self._logger.debug(f'Inserting newline in location {self._cursor_text_pos_x}') new_line_1 = current_line[:self._cursor_text_pos_x] new_line_2 = current_line[self._cursor_text_pos_x:] @@ -1278,7 +1278,7 @@ def _handle_backspace(self): """ current_line = self.get_current_line() - self._logger.info('Inserting backspace in location {}'.format(self._cursor_text_pos_x)) + self._logger.debug(f'Inserting backspace in location {self._cursor_text_pos_x}') if self._cursor_text_pos_x == 0 and self._cursor_text_pos_y != 0: self._cursor_text_pos_x = len(self._text_lines[self._cursor_text_pos_y - 1]) @@ -1301,7 +1301,7 @@ def _handle_home(self): """Function that handles recieving a home keypress """ - self._logger.info('Inserting Home') + self._logger.debug('Inserting Home') self._cursor_x = self._cursor_max_left self._cursor_text_pos_x = 0 @@ -1313,7 +1313,7 @@ def _handle_end(self): """ current_line = self.get_current_line() - self._logger.info('Inserting End') + self._logger.debug('Inserting End') self._cursor_text_pos_x = len(current_line) if len(current_line) > self._viewport_width: @@ -1328,7 +1328,7 @@ def _handle_delete(self): """ current_line = self.get_current_line() - self._logger.info('Inserting delete to pos {}'.format(self._cursor_text_pos_x)) + self._logger.debug(f'Inserting delete to pos {self._cursor_text_pos_x}') if self._cursor_text_pos_x == len(current_line) and self._cursor_text_pos_y < len(self._text_lines) - 1: self._text_lines[self._cursor_text_pos_y] = self._text_lines[self._cursor_text_pos_y] + self._text_lines[self._cursor_text_pos_y + 1] @@ -1347,7 +1347,7 @@ def _insert_char(self, key_pressed): """ current_line = self.get_current_line() - self._logger.info('Inserting character {} to pos {}'.format(chr(key_pressed), self._cursor_text_pos_x)) + self._logger.debug(f'Inserting character {chr(key_pressed)} to pos {self._cursor_text_pos_x}') self.set_text_line(current_line[:self._cursor_text_pos_x] + chr(key_pressed) + current_line[self._cursor_text_pos_x:]) if len(current_line) <= self._viewport_width: diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 846a6c5..5718bde 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -123,7 +123,7 @@ def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, pad A reference to the created scroll menu object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_scroll_menu = widgets.ScrollMenu(id, title, self._grid, @@ -137,7 +137,7 @@ def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, pad self._widgets[id] = new_scroll_menu if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_scroll_menu)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}') return new_scroll_menu @@ -169,7 +169,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= A reference to the created checkbox object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_checkbox_menu = widgets.CheckBoxMenu(id, title, self._grid, @@ -184,7 +184,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= self._widgets[id] = new_checkbox_menu if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_checkbox_menu)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_checkbox_menu))}') return new_checkbox_menu @@ -218,7 +218,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = A reference to the created textbox object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_text_box = widgets.TextBox(id, title, self._grid, @@ -232,7 +232,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = self._widgets[id] = new_text_box if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_text_box)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_box))}') return new_text_box @@ -264,7 +264,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx A reference to the created textblock object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_text_block = widgets.ScrollTextBlock(id, title, self._grid, @@ -279,7 +279,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx self._widgets[id] = new_text_block if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_text_block)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_text_block))}') return new_text_block @@ -309,7 +309,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, A reference to the created label object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_label = widgets.Label(id, title, self._grid, @@ -321,7 +321,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady, self._logger) self._widgets[id] = new_label - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_label)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') return new_label @@ -353,7 +353,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad A reference to the created block label object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_label = widgets.BlockLabel(id, title, self._grid, @@ -366,7 +366,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad center, self._logger) self._widgets[id] = new_label - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_label)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_label))}') return new_label @@ -398,7 +398,7 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 A reference to the created button object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = len(self.get_widgets().keys()) new_button = widgets.Button(id, title, self._grid, @@ -413,7 +413,7 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 self._widgets[id] = new_button if self._selected_widget is None: self.set_selected_widget(id) - self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_button)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_button))}') return new_button @@ -470,6 +470,5 @@ def add_slider(self, title, row, column, row_span=1, step, init_val) self._widgets[id] = new_slider - self._logger.info('Adding widget {} w/ ID {} of type {}' - .format(title, id, str(type(new_slider)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_slider))}') return new_slider From 78c748cea2fcd28e1603615b5cd4858914970dae Mon Sep 17 00:00:00 2001 From: jwlodek Date: Mon, 17 May 2021 09:46:37 -0400 Subject: [PATCH 08/40] Fix typo --- py_cui/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py_cui/ui.py b/py_cui/ui.py index 4a0ddeb..b63b6d7 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -1195,7 +1195,7 @@ def _move_left(self): self._viewport_x_start = self._viewport_x_start - 1 self._cursor_text_pos_x = self._cursor_text_pos_x - 1 - self._logger.debug(f'Moved cursor left to pos {self._cursor_text_pos_x}) + self._logger.debug(f'Moved cursor left to pos {self._cursor_text_pos_x}') def _move_right(self): From 36aba12818d03035c8f17e5bc377c74870cf5ebf Mon Sep 17 00:00:00 2001 From: jwlodek Date: Mon, 17 May 2021 09:47:51 -0400 Subject: [PATCH 09/40] Remove python 3.5 CI testing, add python 3.9 --- .github/workflows/py_cui_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/py_cui_test.yml b/.github/workflows/py_cui_test.yml index 3fe7496..0155944 100644 --- a/.github/workflows/py_cui_test.yml +++ b/.github/workflows/py_cui_test.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 From 7e6237ea3bc99f6b245d7e43bea8e7b1a10eb2c9 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Mon, 17 May 2021 09:52:53 -0400 Subject: [PATCH 10/40] Add missing space to titled top border --- py_cui/renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py_cui/renderer.py b/py_cui/renderer.py index 6e19538..aad30e1 100644 --- a/py_cui/renderer.py +++ b/py_cui/renderer.py @@ -219,7 +219,7 @@ def _draw_border_top(self, ui_element, y, with_title): self._stdscr.addstr(y, start_x + padx, render_text) else: render_text = f'{self._border_characters["UP_LEFT"]}{2 * self._border_characters["HORIZONTAL"]}' \ - f'{title} {self._border_characters["HORIZONTAL"] * (width - 6 - 2 * padx - len(title))}' \ + f' {title} {self._border_characters["HORIZONTAL"] * (width - 6 - 2 * padx - len(title))}' \ f'{self._border_characters["UP_RIGHT"]}' self._stdscr.addstr(y, start_x + padx, render_text) From 7459d1e6b7e1e1e71a484a2c3a6176f86a26652f Mon Sep 17 00:00:00 2001 From: jwlodek Date: Mon, 17 May 2021 10:33:54 -0400 Subject: [PATCH 11/40] Add option for setting selection change event function for scroll menus, add some try/except blocks to prevent renderer re-assignment --- py_cui/__init__.py | 16 +++++++++++----- py_cui/widgets.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index cec1c68..efd742a 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -390,11 +390,17 @@ def _initialize_widget_renderer(self): self._renderer = py_cui.renderer.Renderer(self, self._stdscr, self._logger) for widget_id in self.get_widgets().keys(): if self.get_widgets()[widget_id] is not None: - self.get_widgets()[widget_id]._assign_renderer(self._renderer) - 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) + try: + self.get_widgets()[widget_id]._assign_renderer(self._renderer) + except py_cui.errors.PyCUIError: + 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') def toggle_unicode_borders(self): diff --git a/py_cui/widgets.py b/py_cui/widgets.py index c9b3aa3..09754e2 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -22,6 +22,7 @@ import curses +from typing import Callable import py_cui import py_cui.ui import py_cui.colors @@ -404,9 +405,30 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa Widget.__init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger) py_cui.ui.MenuImplementation.__init__(self, logger) + self._on_selection_change = None self.set_help_text('Focus mode on ScrollMenu. Use Up/Down/PgUp/PgDown/Home/End to scroll, Esc to exit.') + def set_on_selection_change_event(self, on_selection_change_event): + """Function that sets the function fired when menu selection changes, with the new selection as an arg + + Parameters + ---------- + on_selection_change_event : Callable + Callable function that takes in as an argument the newly selected element + + Raises + ------ + TypeError + Raises a type error if event function is not callable + """ + + if not isinstance(on_selection_change_event, Callable): + raise TypeError('On selection change event must be a Callable!') + + self._on_selection_change = on_selection_change_event + + def _handle_mouse_press(self, x, y): """Override of base class function, handles mouse press in menu @@ -417,10 +439,16 @@ def _handle_mouse_press(self, x, y): """ super()._handle_mouse_press(x, y) + + current = self.get_selected_item_index() viewport_top = self._start_y + self._pady + 1 + 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._on_selection_change(self.get()) def _handle_key_press(self, key_pressed): @@ -435,7 +463,10 @@ def _handle_key_press(self, key_pressed): """ super()._handle_key_press(key_pressed) + + 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: @@ -448,6 +479,8 @@ def _handle_key_press(self, key_pressed): self._jump_up() if key_pressed == py_cui.keys.KEY_PAGE_DOWN: self._jump_down(viewport_height) + if self.get_selected_item_index() != current and self._on_selection_change is not None: + self._on_selection_change(self.get()) def _draw(self): From 936dee17442891d0d108a18d086d4f5057d69867 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Fri, 21 May 2021 11:19:42 +0200 Subject: [PATCH 12/40] Converted all `.format()` to `f-strings`. --- docs/developers.md | 16 ++++++++-------- docs/examples.md | 2 +- docs/writing.md | 4 ++-- examples/autogit.py | 28 ++++++++++++++-------------- examples/simple_todo_list.py | 2 +- examples/snano.py | 12 ++++++------ py_cui/controls/slider.py | 5 ++--- py_cui/dialogs/filedialog.py | 10 +++++----- py_cui/dialogs/form.py | 6 +++--- py_cui/widget_set.py | 2 +- py_cui/widgets.py | 6 +++--- 11 files changed, 46 insertions(+), 47 deletions(-) diff --git a/docs/developers.md b/docs/developers.md index 4948f63..a896019 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -86,7 +86,7 @@ class MenuImplementation(UIImplementation): if self._selected_item > 0: self._selected_item = self._selected_item - 1 - self._logger.debug('Scrolling up to item {}'.format(self._selected_item)) + self._logger.debug(f'Scrolling up to item {self._selected_item}') def _scroll_down(self, viewport_height): if self._selected_item < len(self._view_items) - 1: @@ -94,22 +94,22 @@ class MenuImplementation(UIImplementation): if self._selected_item > self._top_view + viewport_height: self._top_view = self._top_view + 1 - self._logger.debug('Scrolling down to item {}'.format(self._selected_item)) + self._logger.debug(f'Scrolling down to item {self._selected_item}') def add_item(self, item_text): - self._logger.debug('Adding item {} to menu'.format(item_text)) + self._logger.debug(f'Adding item {item_text} to menu') self._view_items.append(item_text) def add_item_list(self, item_list): - self._logger.debug('Adding item list {} to menu'.format(str(item_list))) + self._logger.debug(f'Adding item list {str(item_list)} to menu') for item in item_list: self.add_item(item) def remove_selected_item(self): if len(self._view_items) == 0: return - self._logger.debug('Removing {}'.format(self._view_items[self._selected_item])) + self._logger.debug(f'Removing {self._view_items[self._selected_item]}') del self._view_items[self._selected_item] if self._selected_item >= len(self._view_items): self._selected_item = self._selected_item - 1 @@ -205,7 +205,7 @@ Finally, add a function to the `PyCUI` class in `__init__.py` that will add the ```Python def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0): - id = 'Widget{}'.format(len(self.get_widgets().keys())) + id = f'Widget{len(self.get_widgets().keys())}' new_scroll_menu = widgets.ScrollMenu( id, title, self._grid, @@ -219,7 +219,7 @@ def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, pad self.get_widgets()[id] = new_scroll_menu if self._selected_widget is None: self.set_selected_widget(id) - self._logger.debug('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_scroll_menu)))) + self._logger.debug(f'Adding widget {title} w/ ID {id} of type {str(type(new_scroll_menu))}' return new_scroll_menu ``` The function must: @@ -247,4 +247,4 @@ This documentation section is incomplete. Feel free to [expand me](https://githu ### Working on color rules -This documentation section is incomplete. Feel free to [expand me](https://github.com/jwlodek/py_cui/pulls). \ No newline at end of file +This documentation section is incomplete. Feel free to [expand me](https://github.com/jwlodek/py_cui/pulls). diff --git a/docs/examples.md b/docs/examples.md index f18c85b..0666ed3 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -51,7 +51,7 @@ class SimpleTodoList: def add_item(self): """ Add a todo item """ - self.todo_scroll_cell.add_item('{}'.format(self.new_todo_textbox.get())) + self.todo_scroll_cell.add_item(f'{self.new_todo_textbox.get()}') self.new_todo_textbox.clear() def mark_as_in_progress(self): diff --git a/docs/writing.md b/docs/writing.md index 0fdaf3a..0a1f0dd 100644 --- a/docs/writing.md +++ b/docs/writing.md @@ -128,7 +128,7 @@ class SimpleTodoList: def add_item(self): """ Add a todo item """ - self.todo_scroll_cell.add_item('{}'.format(self.new_todo_textbox.get())) + self.todo_scroll_cell.add_item(f'{self.new_todo_textbox.get()}') self.new_todo_textbox.clear() @@ -249,4 +249,4 @@ wrapper = MultiWindowDemo(root) root.start() ``` -In addition, it may be worthwhile to create additional wrapper classes for the individual screens in order to better separate the logic for each. An example of this approach would be the [`ScreenManager`](https://github.com/jwlodek/pyautogit/blob/master/pyautogit/screen_manager.py) and various [sub-classes](https://github.com/jwlodek/pyautogit/blob/master/pyautogit/settings_screen.py) for `pyautogit`. \ No newline at end of file +In addition, it may be worthwhile to create additional wrapper classes for the individual screens in order to better separate the logic for each. An example of this approach would be the [`ScreenManager`](https://github.com/jwlodek/pyautogit/blob/master/pyautogit/screen_manager.py) and various [sub-classes](https://github.com/jwlodek/pyautogit/blob/master/pyautogit/settings_screen.py) for `pyautogit`. diff --git a/examples/autogit.py b/examples/autogit.py index a4eafa6..bf325fa 100644 --- a/examples/autogit.py +++ b/examples/autogit.py @@ -33,11 +33,11 @@ def __init__(self, root: py_cui.PyCUI, dir): res = proc.returncode if res != 0: print(res) - print('ERROR - fatal, {} is not a git repository.'.format(self.dir)) + print(f'ERROR - fatal, {self.dir} is not a git repository.') exit() # Set title - self.root.set_title('Autogit v{} - {}'.format(__version__, os.path.basename(self.dir))) + self.root.set_title(f'Autogit v{__version__} - {os.path.basename(self.dir)}') # Keybindings when in overview mode, and set info bar self.root.add_key_command(py_cui.keys.KEY_R_LOWER, self.refresh_git_status) @@ -150,7 +150,7 @@ def show_git_commit_diff(self): proc = Popen(['git', 'diff', commit_val], stdout=PIPE, stderr=PIPE) out, _ = proc.communicate() out = out.decode() - self.diff_text_block.set_title('Git Diff for {}'.format(commit_val)) + self.diff_text_block.set_title(f'Git Diff for {commit_val}') self.diff_text_block.set_text(out) except: self.root.show_warning_popup('Git Failed', 'Unable to read commit diff information') @@ -205,11 +205,11 @@ def create_new_branch(self): _, err = proc.communicate() res = proc.returncode if res != 0: - self.root.show_error_popup('Create Branch Failed Failed', '{}'.format(err)) + self.root.show_error_popup('Create Branch Failed Failed', f'{err}') return self.refresh_git_status(preserve_selected=True) self.new_branch_textbox.clear() - self.root.show_message_popup('Success', 'Checked out branch {}'.format(new_branch_name)) + self.root.show_message_popup('Success', f'Checked out branch {new_branch_name}') except: self.root.show_warning_popup('Git Failed', 'Unable to checkout branch, please check git installation') @@ -228,11 +228,11 @@ def commit_changes(self, commit): _, err = proc.communicate() res = proc.returncode if res != 0: - self.root.show_error_popup('Create Branch Failed Failed', '{}'.format(err)) + self.root.show_error_popup('Create Branch Failed Failed', f'{err}') return self.refresh_git_status(preserve_selected=True) self.commit_message_box.clear() - self.root.show_message_popup('Success', 'Commited: {}'.format(message)) + self.root.show_message_popup('Success', f'Commited: {message}') else: self.root.show_message_popup('Cancelled', 'Commit Operation cancelled') @@ -244,10 +244,10 @@ def checkout_branch(self): out, _ = proc.communicate() res = proc.returncode if res != 0: - self.root.show_error_popup('Checkout Failed', '{}'.format(out)) + self.root.show_error_popup('Checkout Failed', f'{out}') return self.refresh_git_status(preserve_selected=True) - self.root.show_message_popup('Success', 'Checked out branch {}'.format(target)) + self.root.show_message_popup('Success', f'Checked out branch {target}') except: self.root.show_warning_popup('Git Failed', 'Unable to checkout branch, please check git installation') @@ -264,7 +264,7 @@ def revert_changes(self): def open_git_diff(self): target = self.add_files_menu.get()[3:] - self.diff_text_block.title = '{} File Diff'.format(target) + self.diff_text_block.title = f'{target} File Diff' proc = Popen(['git', 'diff', target], stdout=PIPE, stderr=PIPE) out, _ = proc.communicate() out = out.decode() @@ -338,10 +338,10 @@ def fetch_branch(self): out, _ = proc.communicate() res = proc.returncode if res != 0: - self.root.show_error_popup('Checkout Failed', '{}'.format(out)) + self.root.show_error_popup('Checkout Failed', f'{out}') return self.refresh_git_status(preserve_selected=True) - self.root.show_message_popup('Success', 'Checked out branch {}'.format(target)) + self.root.show_message_popup('Success', f'Checked out branch {target}') except FileNotFoundError: self.root.show_warning_popup('Git Failed', 'Unable to checkout branch, please check git installation') @@ -356,10 +356,10 @@ def parse_args(): if 'directory' not in args.keys(): return '.' elif not os.path.exists(args['directory']): - print('ERROR - {} path does not exist'.format(args['directory'])) + print(f'ERROR - {args["directory"]} path does not exist') exit() elif not os.path.isdir(args['directory']): - print('ERROR - {} is not a directory'.format(args['directory'])) + print(f'ERROR - {args["directory"]} is not a directory') exit() return args['directory'] diff --git a/examples/simple_todo_list.py b/examples/simple_todo_list.py index 0c3c390..11be298 100644 --- a/examples/simple_todo_list.py +++ b/examples/simple_todo_list.py @@ -81,7 +81,7 @@ def add_item(self): """Add a todo item """ - self.todo_scroll_cell.add_item('{}'.format(self.new_todo_textbox.get())) + self.todo_scroll_cell.add_item(f'{self.new_todo_textbox.get()}') def remove_item(self): diff --git a/examples/snano.py b/examples/snano.py index 0abb5a3..02636ae 100644 --- a/examples/snano.py +++ b/examples/snano.py @@ -66,10 +66,10 @@ def open_new_directory(self): if len(target) == 0: target = '.' elif not os.path.exists(target): - self.root.show_error_popup('Does not exist', 'ERROR - {} path does not exist'.format(target)) + self.root.show_error_popup('Does not exist', f'ERROR - {target} path does not exist') return elif not os.path.isdir(target): - self.root.show_error_popup('Not a Dir', 'ERROR - {} is not a directory'.format(target)) + self.root.show_error_popup('Not a Dir', f'ERROR - {target} is not a directory') return target = os.path.abspath(target) self.current_dir_box.set_text(target) @@ -128,7 +128,7 @@ def save_opened_file(self): fp = open(os.path.join(self.dir, self.edit_text_block.get_title()), 'w') fp.write(self.edit_text_block.get()) fp.close() - self.root.show_message_popup('Saved', 'Your file has been saved as {}'.format(self.edit_text_block.get_title())) + self.root.show_message_popup('Saved', f'Your file has been saved as {self.edit_text_block.get_title()}') else: self.root.show_error_popup('No File Opened', 'Please open a file before saving it.') @@ -156,10 +156,10 @@ def parse_args(): if 'directory' not in args.keys(): return '.' elif not os.path.exists(args['directory']): - print('ERROR - {} path does not exist'.format(args['directory'])) + print(f'ERROR - {args["directory"]} path does not exist') exit() elif not os.path.isdir(args['directory']): - print('ERROR - {} is not a directory'.format(args['directory'])) + print(f'ERROR - {args["directory"]} is not a directory') exit() return args['directory'] @@ -169,7 +169,7 @@ def parse_args(): # Initialize the PyCUI object, and set the title root = py_cui.PyCUI(7, 8) -root.set_title('Super Nano v{}'.format(__version__)) +root.set_title(f'Super Nano v{__version__}') # Create the wrapper instance object. frame = SuperNano(root, dir) diff --git a/py_cui/controls/slider.py b/py_cui/controls/slider.py index 3e6e6dc..c48e9f8 100644 --- a/py_cui/controls/slider.py +++ b/py_cui/controls/slider.py @@ -17,8 +17,7 @@ def __init__(self, min_val, max_val, init_val, step, logger): if self._cur_val < self._min_val or self._cur_val > self._max_val: raise py_cui.errors.PyCUIInvalidValue( - 'initial value must be between {} and {}' - .format(self._min_val, self._max_val)) + f'initial value must be between {self._min_val} and {self._max_val}') def set_bar_char(self, char): @@ -31,7 +30,7 @@ def set_bar_char(self, char): Character to represent progressive bar. """ - assert len(char) == 1, "char should contain exactly one character, got {} instead.".format(len(char)) + assert len(char) == 1, f"char should contain exactly one character, got {len(char)} instead." self._bar_char = char diff --git a/py_cui/dialogs/filedialog.py b/py_cui/dialogs/filedialog.py index 6f8946a..45c6aa2 100644 --- a/py_cui/dialogs/filedialog.py +++ b/py_cui/dialogs/filedialog.py @@ -93,9 +93,9 @@ def __str__(self): """ if self._type == 'file': - return '{} {}'.format(self._file_icon, self._name) + return f'{self._file_icon} {self._name}' else: - return '{} {}'.format(self._folder_icon, self._name) + return f'{self._folder_icon} {self._name}' @@ -251,7 +251,7 @@ def _handle_key_press(self, key_pressed): self._current_dir = old_dir self.refresh_view() except PermissionError: - self._parent_dialog.display_warning('Permission Error Accessing: {} !'.format(self._current_dir)) + self._parent_dialog.display_warning(f'Permission Error Accessing: {self._current_dir} !') self._current_dir = old_dir self.refresh_view() finally: @@ -638,7 +638,7 @@ def __init__(self, root, callback, initial_dir, dialog_type, ascii_icons, limit_ self._filename_input = FileNameInput(self, input_title, '', renderer, logger) self._file_dir_select = FileSelectElement(self, initial_dir, dialog_type, ascii_icons, title, color, None, renderer, logger, limit_extensions=limit_extensions) self._submit_button = FileDialogButton(self, title, self._submit, 1, '', 'OK', renderer, logger) - self._cancel_button = FileDialogButton(self, 'Cancel {}'.format(title), self._root.close_popup, 2, '', 'ESC', renderer, logger) + self._cancel_button = FileDialogButton(self, f'Cancel {title}', self._root.close_popup, 2, '', 'ESC', renderer, logger) # Internal popup used for secondary errors and warnings self._internal_popup = None @@ -830,4 +830,4 @@ def _draw(self): self._internal_popup._draw() self._renderer.unset_color_mode(self._color) -#class FileDialogWidget \ No newline at end of file +#class FileDialogWidget diff --git a/py_cui/dialogs/form.py b/py_cui/dialogs/form.py index 03f8d3d..41ea09f 100644 --- a/py_cui/dialogs/form.py +++ b/py_cui/dialogs/form.py @@ -61,7 +61,7 @@ def is_valid(self): msg = None if len(self._text) == 0 and self.is_required(): - msg = 'Field <{}> cannot be empty!'.format(self.get_fieldname()) + msg = f'Field <{self.get_fieldname()}> cannot be empty!' return msg is None, msg @@ -482,7 +482,7 @@ def _handle_key_press(self, key_pressed): self._internal_popup = InternalFormPopup(self, self._root, err_msg, - 'Required fields: {}'.format(str(self._required_fields)), + f'Required fields: {str(self._required_fields)}', py_cui.YELLOW_ON_BLACK, self._renderer, self._logger) @@ -532,4 +532,4 @@ def _draw(self): self._form_fields[self.get_selected_form_index()]._draw() if self._internal_popup is not None: - self._internal_popup._draw() \ No newline at end of file + self._internal_popup._draw() diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 5718bde..8146158 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -454,7 +454,7 @@ def add_slider(self, title, row, column, row_span=1, A reference to the created slider object. """ - id = 'Widget{}'.format(len(self._widgets.keys())) + id = f'Widget{len(self._widgets.keys())}' new_slider = controls.slider.SliderWidget(id, title, self._grid, diff --git a/py_cui/widgets.py b/py_cui/widgets.py index c9b3aa3..45d1518 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -64,7 +64,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self._grid = grid grid_rows, grid_cols = self._grid.get_dimensions() if (grid_cols < column + column_span) or (grid_rows < row + row_span): - raise py_cui.errors.PyCUIOutOfBoundsError("Target grid too small for widget {}".format(title)) + raise py_cui.errors.PyCUIOutOfBoundsError(f"Target grid too small for widget {title}") self._row = row self._column = column @@ -554,9 +554,9 @@ def _draw(self): line_counter = 0 for item in self._view_items: if self._selected_item_dict[item]: - line = '[{}] - {}'.format(self._checked_char, str(item)) + line = f'[{self._checked_char}] - {str(item)}' else: - line = '[ ] - {}'.format(str(item)) + line = f'[ ] - {str(item)}' if line_counter < self._top_view: line_counter = line_counter + 1 else: From 09aad65a3722492d3335a08338bd2971e6b6ed2d Mon Sep 17 00:00:00 2001 From: jwlodek Date: Fri, 21 May 2021 16:02:53 -0400 Subject: [PATCH 13/40] Initial work to support different mouse operations --- py_cui/__init__.py | 6 +++--- py_cui/keys.py | 16 ++++++++++++++ py_cui/ui.py | 18 ++-------------- py_cui/widgets.py | 52 +++++++++++++++++++++++++++++++++++++--------- 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index efd742a..fb8e180 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1631,16 +1631,16 @@ def _draw(self, stdscr): # 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') - _, x, y, _, _ = curses.getmouse() + id, x, y, _, mouse_event = curses.getmouse() in_element = self.get_element_at_position(x, y) # In first case, we click inside already selected widget, pass click for processing if in_element is not None and in_element.is_selected(): - in_element._handle_mouse_press(x, y) + 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): self.move_focus(in_element) - in_element._handle_mouse_press(x, y) + 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: diff --git a/py_cui/keys.py b/py_cui/keys.py index f85901d..2fb9789 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -252,6 +252,22 @@ def get_char_from_ascii(key_num): NUMBER_KEYS = [KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9] ALPHANUMERIC_KEYS = LETTER_KEYS + NUMBER_KEYS +# Mouse click events +LEFT_MOUSE_CLICK = curses.BUTTON1_CLICKED +LEFT_MOUSE_DBL_CLICK = curses.BUTTON1_DOUBLE_CLICKED +LEFT_MOUSE_TRPL_CLICK = curses.BUTTON1_TRIPLE_CLICKED +LEFT_MOUSE_PRESSED = curses.BUTTON1_PRESSED +LEFT_MOUSE_RELEASED = curses.BUTTON1_RELEASED + +RIGHT_MOUSE_CLICK = curses.BUTTON2_CLICKED +RIGHT_MOUSE_DBL_CLICK = curses.BUTTON2_DOUBLE_CLICKED +RIGHT_MOUSE_TRPL_CLICK = curses.BUTTON2_TRIPLE_CLICKED +RIGHT_MOUSE_PRESSED = curses.BUTTON2_PRESSED +RIGHT_MOUSE_RELEASED = curses.BUTTON2_RELEASED + +MOUSE_EVENTS = [LEFT_MOUSE_CLICK, LEFT_MOUSE_DBL_CLICK, LEFT_MOUSE_TRPL_CLICK, LEFT_MOUSE_PRESSED, LEFT_MOUSE_RELEASED, + RIGHT_MOUSE_CLICK, RIGHT_MOUSE_DBL_CLICK, RIGHT_MOUSE_TRPL_CLICK, RIGHT_MOUSE_PRESSED, RIGHT_MOUSE_RELEASED] + # Pressing backspace returns 8 on windows? if platform == 'win32': KEY_BACKSPACE = 8 diff --git a/py_cui/ui.py b/py_cui/ui.py index b63b6d7..64c5668 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -67,7 +67,6 @@ def __init__(self, id, title, renderer, logger): self._border_color = self._color self._focus_border_color = self._color self._selected_color = self._color - self._mouse_press_handler = None self._selected = False self._renderer = renderer self._logger = logger @@ -369,19 +368,7 @@ def _handle_key_press(self, key_pressed): raise NotImplementedError - def add_mouse_press_handler(self, mouse_press_handler_func): - """Sets a mouse press handler function - - Parameters - ---------- - mouse_press_handler_func : function / lambda function - Function that takes 2 parameters: x and y of a mouse press. Executes when mouse pressed and element is selected - """ - - self._mouse_press_handler = mouse_press_handler_func - - - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Can be implemented by subclass. Used to handle mouse presses Parameters @@ -390,8 +377,7 @@ def _handle_mouse_press(self, x, y): Coordinates of the mouse press event. """ - if self._mouse_press_handler is not None: - self._mouse_press_handler(x, y) + pass def _draw(self): diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 09754e2..383146b 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -75,6 +75,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self._pady = pady self._selectable = selectable self._key_commands = {} + self._mouse_commands = {} self._text_color_rules = [] self._default_color = py_cui.WHITE_ON_BLACK self._border_color = self._default_color @@ -92,9 +93,35 @@ def add_key_command(self, key, command): a non-argument function or lambda function to execute if in focus mode and key is pressed """ + self._key_commands[key] = command + def add_mouse_command(self, mouse_event, command): + """Maps a keycode to a function that will be executed when in focus mode + + Parameters + ---------- + key : py_cui.keys.MOUSE_EVENT + Mouse event code from py_cui.keys + command : Callable + a non-argument function or lambda function to execute if in focus mode and key is pressed + + Raises + ------ + PyCUIError + If input mouse event code is not valid + """ + + if mouse_event not in py_cui.keys.MOUSE_EVENTS: + raise py_cui.errors.PyCUIError(f'Event code {mouse_event} is not a valid py_cui mouse event!') + + if mouse_event in self._mouse_commands.keys(): + self._logger.warn(f'Overriding mouse command for event {mouse_event}') + + self._mouse_commands[mouse_event] = command + + def update_key_command(self, key, command): """Maps a keycode to a function that will be executed when in focus mode, if key is already mapped @@ -429,7 +456,7 @@ def set_on_selection_change_event(self, on_selection_change_event): self._on_selection_change = on_selection_change_event - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Override of base class function, handles mouse press in menu Parameters @@ -438,17 +465,22 @@ def _handle_mouse_press(self, x, y): Coordinates of mouse press """ - super()._handle_mouse_press(x, y) + # For either click or double click we want to jump to the clicked-on item + if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK or mouse_event == py_cui.keys.LEFT_MOUSE_DBL_CLICK: + current = self.get_selected_item_index() + viewport_top = self._start_y + self._pady + 1 - current = self.get_selected_item_index() - viewport_top = self._start_y + self._pady + 1 - - 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 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._on_selection_change(self.get()) + if self.get_selected_item_index() != current and self._on_selection_change is not None: + self._on_selection_change(self.get()) + + # For scroll menu, handle custom mouse press after initial event, since we will likely want to + # have access to the newly selected item + super()._handle_mouse_press(x, y, mouse_event) + def _handle_key_press(self, key_pressed): From 819d1642da843b81f01244cac68c15b409a006ce Mon Sep 17 00:00:00 2001 From: PabloLec Date: Wed, 26 May 2021 15:28:13 +0200 Subject: [PATCH 14/40] Small modifications to handle `exit_key` set to None. --- py_cui/__init__.py | 11 ++++++++--- py_cui/keys.py | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index cec1c68..3e81de8 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -139,9 +139,14 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, # Add status and title bar self.title_bar = py_cui.statusbar.StatusBar(self._title, BLACK_ON_WHITE) exit_key_char = py_cui.keys.get_char_from_ascii(exit_key) - self._init_status_bar_text = f'Press - {exit_key_char} - to exit. Arrow Keys to move ' \ - 'between widgets. Enter to enter focus ' \ - 'mode.' + + 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.' + else: + 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) diff --git a/py_cui/keys.py b/py_cui/keys.py index f85901d..9b12707 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -40,7 +40,10 @@ def get_char_from_ascii(key_num): char : character character converted from ascii """ - + + if key_num is None: + return None + return chr(key_num) From f408977792014b2bfde341935e53f9d5e5fd9ada Mon Sep 17 00:00:00 2001 From: jwlodek Date: Wed, 26 May 2021 12:10:45 -0400 Subject: [PATCH 15/40] Adding fixes for unit tests that were broken by changes to Widget ID format --- tests/test_core/test_py_cui_core.py | 42 ++++++++++++++--------------- tests/test_core/test_widget_set.py | 42 ++++++++++++++--------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/test_core/test_py_cui_core.py b/tests/test_core/test_py_cui_core.py index 636efe4..c41a5f8 100644 --- a/tests/test_core/test_py_cui_core.py +++ b/tests/test_core/test_py_cui_core.py @@ -29,11 +29,11 @@ def test_add_scroll_menu(PYCUI): test_cui.add_scroll_menu('Demo', 1, 1) assert len(test_cui.get_widgets().keys()) == 1 for key in test_cui.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_cui.get_widgets()['Widget0'] + widget = test_cui.get_widgets()[0] assert isinstance(widget, py_cui.widgets.ScrollMenu) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -44,11 +44,11 @@ def test_add_checkbox_menu(PYCUI): test_cui.add_checkbox_menu('Demo', 1, 1) assert len(test_cui.get_widgets().keys()) == 1 for key in test_cui.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_cui.get_widgets()['Widget0'] + widget = test_cui.get_widgets()[0] assert isinstance(widget, py_cui.widgets.CheckBoxMenu) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -59,11 +59,11 @@ def test_add_label(PYCUI): test_cui.add_label('Demo', 1, 1) assert len(test_cui.get_widgets().keys()) == 1 for key in test_cui.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_cui.get_widgets()['Widget0'] + widget = test_cui.get_widgets()[0] assert isinstance(widget, py_cui.widgets.Label) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -74,11 +74,11 @@ def test_add_block_label(PYCUI): test_cui.add_block_label('Demo', 1, 1) assert len(test_cui.get_widgets().keys()) == 1 for key in test_cui.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_cui.get_widgets()['Widget0'] + widget = test_cui.get_widgets()[0] assert isinstance(widget, py_cui.widgets.BlockLabel) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -89,11 +89,11 @@ def test_add_text_box(PYCUI): test_cui.add_text_box('Demo', 1, 1) assert len(test_cui.get_widgets().keys()) == 1 for key in test_cui.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_cui.get_widgets()['Widget0'] + widget = test_cui.get_widgets()[0] assert isinstance(widget, py_cui.widgets.TextBox) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -104,11 +104,11 @@ def test_add_button(PYCUI): test_cui.add_button('Demo', 1, 1) assert len(test_cui.get_widgets().keys()) == 1 for key in test_cui.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_cui.get_widgets()['Widget0'] + widget = test_cui.get_widgets()[0] assert isinstance(widget, py_cui.widgets.Button) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -119,11 +119,11 @@ def test_add_text_block(PYCUI): test_cui.add_text_block('Demo', 1, 1) assert len(test_cui.get_widgets().keys()) == 1 for key in test_cui.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_cui.get_widgets()['Widget0'] + widget = test_cui.get_widgets()[0] assert isinstance(widget, py_cui.widgets.ScrollTextBlock) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 diff --git a/tests/test_core/test_widget_set.py b/tests/test_core/test_widget_set.py index a1ad6a2..271c0dc 100644 --- a/tests/test_core/test_widget_set.py +++ b/tests/test_core/test_widget_set.py @@ -15,11 +15,11 @@ def test_add_scroll_menu(WIDGETSET): test_widget_set.add_scroll_menu('Demo', 1, 1) assert len(test_widget_set.get_widgets().keys()) == 1 for key in test_widget_set.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_widget_set.get_widgets()['Widget0'] + widget = test_widget_set.get_widgets()[0] assert isinstance(widget, py_cui.widgets.ScrollMenu) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -30,11 +30,11 @@ def test_add_checkbox_menu(WIDGETSET): test_widget_set.add_checkbox_menu('Demo', 1, 1) assert len(test_widget_set.get_widgets().keys()) == 1 for key in test_widget_set.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_widget_set.get_widgets()['Widget0'] + widget = test_widget_set.get_widgets()[0] assert isinstance(widget, py_cui.widgets.CheckBoxMenu) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -45,11 +45,11 @@ def test_add_label(WIDGETSET): test_widget_set.add_label('Demo', 1, 1) assert len(test_widget_set.get_widgets().keys()) == 1 for key in test_widget_set.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_widget_set.get_widgets()['Widget0'] + widget = test_widget_set.get_widgets()[0] assert isinstance(widget, py_cui.widgets.Label) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -60,11 +60,11 @@ def test_add_block_label(WIDGETSET): test_widget_set.add_block_label('Demo', 1, 1) assert len(test_widget_set.get_widgets().keys()) == 1 for key in test_widget_set.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_widget_set.get_widgets()['Widget0'] + widget = test_widget_set.get_widgets()[0] assert isinstance(widget, py_cui.widgets.BlockLabel) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -75,11 +75,11 @@ def test_add_text_box(WIDGETSET): test_widget_set.add_text_box('Demo', 1, 1) assert len(test_widget_set.get_widgets().keys()) == 1 for key in test_widget_set.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_widget_set.get_widgets()['Widget0'] + widget = test_widget_set.get_widgets()[0] assert isinstance(widget, py_cui.widgets.TextBox) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -90,11 +90,11 @@ def test_add_button(WIDGETSET): test_widget_set.add_button('Demo', 1, 1) assert len(test_widget_set.get_widgets().keys()) == 1 for key in test_widget_set.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_widget_set.get_widgets()['Widget0'] + widget = test_widget_set.get_widgets()[0] assert isinstance(widget, py_cui.widgets.Button) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 @@ -105,11 +105,11 @@ def test_add_text_block(WIDGETSET): test_widget_set.add_text_block('Demo', 1, 1) assert len(test_widget_set.get_widgets().keys()) == 1 for key in test_widget_set.get_widgets().keys(): - assert key == 'Widget0' + assert key == 0 break - widget = test_widget_set.get_widgets()['Widget0'] + widget = test_widget_set.get_widgets()[0] assert isinstance(widget, py_cui.widgets.ScrollTextBlock) - assert widget.get_id() == 'Widget0' + assert widget.get_id() == 0 row, col = widget.get_grid_cell() assert row == 1 assert col == 1 From 67812aa55b4259599a345edbb4eecdad1da23dc7 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Mon, 31 May 2021 11:44:10 +0200 Subject: [PATCH 16/40] Status bars can now be shown/hidden and UI is resized accordingly. --- py_cui/__init__.py | 77 ++++++++++-------------- py_cui/grid.py | 5 +- py_cui/statusbar.py | 45 ++++++++++++-- py_cui/widget_set.py | 8 ++- py_cui/widgets.py | 7 +-- tests/conftest.py | 4 +- tests/test_ui_elements/test_statusbar.py | 2 +- 7 files changed, 88 insertions(+), 60 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 3e81de8..36630e6 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -130,14 +130,8 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, height = simulated_terminal[0] width = simulated_terminal[1] - # Init terminal height width. Subtract 4 from height - # for title/status bar and padding - self._height = height - self._width = width - self._height = self._height - 4 - # Add status and title bar - self.title_bar = py_cui.statusbar.StatusBar(self._title, BLACK_ON_WHITE) + 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: @@ -149,7 +143,13 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, 'Enter to enter focus mode.' \ self.status_bar = py_cui.statusbar.StatusBar(self._init_status_bar_text, - BLACK_ON_WHITE) + 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 # Logging object initialization for py_cui self._logger = py_cui.debug._initialize_logger(self, @@ -268,21 +268,7 @@ def apply_widget_set(self, new_widget_set): self._grid = new_widget_set._grid self._keybindings = new_widget_set._keybindings - 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 - 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 = height - 4 - - self._refresh_height_width(height, width) + self._refresh_height_width() if self._stdscr is not None: self._initialize_widget_renderer() self._selected_widget = new_widget_set._selected_widget @@ -310,7 +296,7 @@ def create_new_widget_set(self, num_rows, num_cols): """ # Use current logging object and simulated terminal for sub-widget sets - return py_cui.widget_set.WidgetSet(num_rows, num_cols, self._logger, + return py_cui.widget_set.WidgetSet(num_rows, num_cols, self._logger, root=self, simulated_terminal=self._simulated_terminal) @@ -1398,16 +1384,24 @@ def close_popup(self): self._popup = None - def _refresh_height_width(self, height, width): - """Function that updates the height and width of the CUI based on terminal window size + def _refresh_height_width(self): + """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 + 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] - Parameters - ---------- - height : int - Window height in terminal characters - width : int - Window width in terminal characters - """ + 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 @@ -1465,12 +1459,12 @@ def _draw_status_bars(self, stdscr, height, width): Window width in terminal characters """ - if self.status_bar is not 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.attroff(curses.color_pair(self.status_bar.get_color())) - if self.title_bar is not None: + if self.title_bar is not None and self.title_bar.get_height() > 0: stdscr.attron(curses.color_pair(self.title_bar.get_color())) stdscr.addstr(0, 0, fit_text(width, self._title, center=True)) stdscr.attroff(curses.color_pair(self.title_bar.get_color())) @@ -1602,14 +1596,6 @@ def _draw(self, stdscr): # Initialization and size adjustment stdscr.erase() - # find height width, adjust if status/title bar added. We decrement the height by 4 to account for status/title bar and padding - if self._simulated_terminal is None: - height, width = stdscr.getmaxyx() - else: - height = self._simulated_terminal[0] - width = self._simulated_terminal[1] - - height = height - 4 # If the user defined an update function to fire on each draw call, # Run it here. This can of course be also handled user-side @@ -1620,9 +1606,8 @@ def _draw(self, stdscr): # This is what allows the CUI to be responsive. Adjust grid size based on current terminal size # Resize the grid and the widgets if there was a resize operation if key_pressed == curses.KEY_RESIZE: - self._logger.debug(f'Resizing CUI to new dimensions {height} by {width}') try: - self._refresh_height_width(height, width) + self._refresh_height_width() except py_cui.errors.PyCUIOutOfBoundsError as e: self._logger.info('Resized terminal too small') self._display_window_warning(stdscr, str(e)) @@ -1658,7 +1643,7 @@ def _draw(self, stdscr): try: # Draw status/title bar, and all widgets. Selected widget will be bolded. - self._draw_status_bars(stdscr, height, width) + self._draw_status_bars(stdscr, self._height, self._width) self._draw_widgets() # draw the popup if required if self._popup is not None: diff --git a/py_cui/grid.py b/py_cui/grid.py index cb3e5a0..f2cba4d 100644 --- a/py_cui/grid.py +++ b/py_cui/grid.py @@ -23,6 +23,8 @@ class Grid: The number of additional characters found by height mod rows and width mod columns _row_height, _column_width : int The number of characters in a single grid row, column + _title_bar_offset : int + Title bar row offset. Defaults to 1. Set to 0 if title bar is hidden. _logger : py_cui.debug.PyCUILogger logger object for maintaining debug messages """ @@ -51,6 +53,7 @@ def __init__(self, num_rows, num_columns, height, width, logger): self._offset_y = self._height % self._num_rows - 1 self._row_height = int(self._height / self._num_rows) self._column_width = int(self._width / self._num_columns) + self._title_bar_offset = 1 self._logger = logger @@ -185,4 +188,4 @@ def update_grid_height_width(self, height, width): self._offset_x = self._width % self._num_columns self._offset_y = self._height % self._num_rows self._logger.debug(f'Updated grid. Cell dims: {self._row_height}x{self._column_width}, \ - Offsets {self._offset_x},{self._offset_y}') \ No newline at end of file + Offsets {self._offset_x},{self._offset_y}') diff --git a/py_cui/statusbar.py b/py_cui/statusbar.py index 6b5fb89..0ebdd8b 100644 --- a/py_cui/statusbar.py +++ b/py_cui/statusbar.py @@ -16,19 +16,26 @@ class StatusBar: status bar text color : py_cui.COLOR color to display the statusbar + root : py_cui.PyCUI + Main PyCUI object reference + is_title_bar : bool + Is the StatusBar displayed on the top of the grid """ - def __init__(self, text, color): + def __init__(self, text, color, root, is_title_bar=False): """Initializer for statusbar """ self.__text = text self.__color = color + self.__height = 1 + self.__root = root + self.__is_title_bar = is_title_bar def get_color(self): """Getter for status bar color - + Returns ------- color : int @@ -39,7 +46,7 @@ def get_color(self): def get_text(self): - """Getter for stattus bar text + """Getter for status bar text Returns ------- @@ -71,4 +78,34 @@ def set_text(self, text): New statusbar text """ - self.__text = text \ No newline at end of file + self.__text = text + + def get_height(self): + """Getter for status bar height in row + + Returns + ------- + height : int + The statusbar height in row + """ + + return self.__height + + def show(self): + """Sets the status bar height to 1""" + + self.__height = 1 + self._refresh_root_size() + + def hide(self): + """Sets the status bar height to 0""" + + self.__height = 0 + self._refresh_root_size() + + def _refresh_root_size(self): + """Resets the grid's title bar offset if needed and calls a UI size update.""" + + if self.__is_title_bar: + self.__root._grid._title_bar_offset = self.__height + self.__root._refresh_height_width() diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 8146158..28aa6e0 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -29,15 +29,18 @@ class WidgetSet: list of keybindings to check against in the main CUI loop height, width : int height of the terminal in characters, width of terminal in characters + root : py_cui.PyCUI + Main PyCUI object reference """ - def __init__(self, num_rows, num_cols, logger, simulated_terminal=None): + def __init__(self, num_rows, num_cols, logger, root, simulated_terminal=None): """Constructor for WidgetSet """ self._widgets = {} self._keybindings = {} + self._root = root self._simulated_terminal = simulated_terminal if self._simulated_terminal is None: @@ -50,7 +53,8 @@ def __init__(self, num_rows, num_cols, logger, simulated_terminal=None): self._height = height self._width = width - self._height = self._height - 4 + 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) diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 45d1518..65ca602 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -1,4 +1,4 @@ -"""Module contatining all core widget classes for py_cui. +"""Module containing all core widget classes for py_cui. Widgets are the basic building blocks of a user interface made with py_cui. This module contains classes for: @@ -53,7 +53,7 @@ class Widget(py_cui.ui.UIElement): def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, selectable = True): """Initializer for base widget class - Calss UIElement superclass initialzier, and then assigns widget to grid, along with row/column info + Class UIElement superclass initializer, and then assigns widget to grid, along with row/column info and color rules and key commands """ @@ -156,8 +156,7 @@ def get_absolute_start_pos(self): y_adjust = offset_y x_pos = self._column * col_width + x_adjust - # Always add two to the y_pos, because we have a title bar + a pad row - y_pos = self._row * row_height + 2 + y_adjust + y_pos = self._row * row_height + y_adjust + self._grid._title_bar_offset + 1 return x_pos, y_pos diff --git a/tests/conftest.py b/tests/conftest.py index 11603ce..1343096 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,7 +56,7 @@ def _PYCUI(rows, cols, height, width): def WIDGETSET(request, LOGGER): def _WIDGETSET(rows, cols, height, width): - return py_cui.widget_set.WidgetSet(rows, cols, LOGGER, simulated_terminal=[height, width]) + return py_cui.widget_set.WidgetSet(rows, cols, LOGGER, root=py_cui.PyCUI(rows, cols), simulated_terminal=[height, width]) return _WIDGETSET @@ -128,4 +128,4 @@ def _COLORRULE(text, rule_type, match_type, color_A=py_cui.RED_ON_BLACK, color_B color_rule = py_cui.colors.ColorRule(text, color_A, color_B, rule_type, match_type, region, whitespace, LOGGER) return color_rule - return _COLORRULE \ No newline at end of file + return _COLORRULE diff --git a/tests/test_ui_elements/test_statusbar.py b/tests/test_ui_elements/test_statusbar.py index 8de7b68..20773d2 100644 --- a/tests/test_ui_elements/test_statusbar.py +++ b/tests/test_ui_elements/test_statusbar.py @@ -7,7 +7,7 @@ def test_status_bar(): - bar = py_cui.statusbar.StatusBar('Hello', py_cui.WHITE_ON_BLACK) + bar = py_cui.statusbar.StatusBar('Hello', py_cui.WHITE_ON_BLACK, root=py_cui.PyCUI) assert bar.get_text() == 'Hello' bar.set_text('Test') assert bar.get_text() == 'Test' From de75e4092557c9be058dc2bb91212a94cf82cea6 Mon Sep 17 00:00:00 2001 From: Pablo Lecolinet Date: Tue, 8 Jun 2021 10:19:07 +0200 Subject: [PATCH 17/40] Remove embedded null character before curses drawing --- py_cui/renderer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py_cui/renderer.py b/py_cui/renderer.py index aad30e1..19fc088 100644 --- a/py_cui/renderer.py +++ b/py_cui/renderer.py @@ -384,6 +384,7 @@ def draw_text(self, ui_element, line, y, centered = False, bordered = True, sele # Each text elem is a list with [text, color] for text_elem in render_text: + text_elem[0] = text_elem[0].replace(chr(0), "") self.set_color_mode(text_elem[1]) # BLACK_ON_WHITE + BOLD is unreadable on windows terminals From d6aedb0492877aa7c30c89c5eab198c77b96ff4f Mon Sep 17 00:00:00 2001 From: jwlodek Date: Tue, 8 Jun 2021 16:23:24 -0400 Subject: [PATCH 18/40] Add support for additional mouse functions --- .gitignore | 3 ++- py_cui/__init__.py | 14 +++++++++---- py_cui/debug.py | 39 +++++++++++++++++++++++----------- py_cui/widgets.py | 52 ++++++++++++++++++++++++++-------------------- 4 files changed, 69 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index c920666..d52d1cb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ npdoc2md/ ecui/ .coverage py_cui_log.txt +*.log venv/ .pytest_cache/ -demo.py \ No newline at end of file +demo.py diff --git a/py_cui/__init__.py b/py_cui/__init__.py index fb8e180..58edd2c 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -110,10 +110,11 @@ class PyCUI: def __init__(self, num_rows, num_cols, auto_focus_buttons=True, exit_key=py_cui.keys.KEY_Q_LOWER, simulated_terminal=None): - """Constructor for PyCUI class + """Initializer for PyCUI class """ 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') @@ -224,7 +225,8 @@ def set_widget_cycle_key(self, forward_cycle_key=None, reverse_cycle_key=None): def set_toggle_live_debug_key(self, toggle_debug_key): self._toggle_live_debug_key = toggle_debug_key - def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging.DEBUG): + + def enable_logging(self, log_file_path='py_cui.log', logging_level = logging.DEBUG, live_debug_key = py_cui.keys.KEY_CTRL_D): """Function enables logging for py_cui library Parameters @@ -238,7 +240,7 @@ def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging try: 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 = py_cui.keys.KEY_CTRL_D + self._toggle_live_debug_key = live_debug_key except PermissionError as e: print(f'Failed to initialize logger: {str(e)}') @@ -883,6 +885,7 @@ def get_element_at_position(self, x, y): if self._popup is not None and self._popup._contains_position(x, y): return self._popup + elif self._popup is None: for widget_id in self.get_widgets().keys(): if self.get_widgets()[widget_id] is not None: @@ -1633,10 +1636,13 @@ def _draw(self, stdscr): self._logger.info('Detected mouse click') id, x, y, _, mouse_event = curses.getmouse() in_element = self.get_element_at_position(x, y) + self._logger.debug(f'In element : {in_element.get_title()}') # In first case, we click inside already selected widget, pass click for processing - if in_element is not None and in_element.is_selected(): + if in_element is not None: + 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 elif in_element is not None and not isinstance(in_element, py_cui.popups.Popup): self.move_focus(in_element) diff --git a/py_cui/debug.py b/py_cui/debug.py index 07f1048..85c40b0 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -86,7 +86,7 @@ class LiveDebugImplementation(py_cui.ui.MenuImplementation): Attributes ---------- - _live_debug_level : int + level : int Debug level at which to display messages. Can be separate from the default logging level _buffer_size : List[str] Number of log messages to keep buffered in the live debug window @@ -98,12 +98,12 @@ def __init__(self, parent_logger): """ super().__init__(parent_logger) - self._live_debug_level = logging.ERROR + self.level = logging.ERROR # Make default buffer size 100 self._buffer_size = 100 - def add_item(self, msg, log_level): + def print_to_buffer(self, msg, log_level): """Override of default MenuImplementation add_item function If items override the buffer pop the oldest log message @@ -259,7 +259,6 @@ def __init__(self, name): """ super(PyCUILogger, self).__init__(name) - self._live_debug_level = logging.ERROR self._live_debug_enabled = False self.py_cui_root = None self._live_debug_element = LiveDebugElement(self) @@ -323,8 +322,8 @@ def info(self, text): """ debug_text = self._get_debug_text(text) - if self._live_debug_level <= logging.INFO and self._live_debug_enabled: - self._live_debug_element.print_to_live_debug_buffer(debug_text, 'INFO') + if self.level <= logging.INFO: + self._live_debug_element.print_to_buffer(debug_text, 'INFO') super().info(debug_text) @@ -338,8 +337,8 @@ def debug(self, text): """ debug_text = self._get_debug_text(text) - if self._live_debug_level <= logging.DEBUG and self._live_debug_enabled: - self._live_debug_element.print_to_live_debug_buffer(debug_text, 'DEBUG') + if self.level <= logging.DEBUG: + self._live_debug_element.print_to_buffer(debug_text, 'DEBUG') super().debug(debug_text) @@ -353,8 +352,8 @@ def warn(self, text): """ debug_text = self._get_debug_text(text) - if self._live_debug_level <= logging.WARN and self._live_debug_enabled: - self._live_debug_element.print_to_live_debug_buffer(debug_text, 'WARN') + if self.level <= logging.WARN: + self._live_debug_element.print_to_buffer(debug_text, 'WARN') super().warn(debug_text) @@ -368,6 +367,22 @@ def error(self, text): """ debug_text = self._get_debug_text(text) - if self._live_debug_level <= logging.ERROR and self._live_debug_enabled: - self._live_debug_element.print_to_live_debug_buffer(debug_text, 'ERROR') + if self.level <= logging.ERROR: + self._live_debug_element.print_to_buffer(debug_text, 'ERROR') super().error(debug_text) + + + def critical(self, text): + """Override of base logger critical function to add hooks for live debug mode + + Parameters + ---------- + text : str + The log text ot display + """ + + debug_text = self._get_debug_text(text) + if self.level <= logging.CRITICAL: + self._live_debug_element.print_to_buffer(debug_text, 'CRITICAL') + super().critical(debug_text) + diff --git a/py_cui/widgets.py b/py_cui/widgets.py index eec2c01..c003350 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -659,6 +659,12 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.set_help_text('Focus mode on Button. Press Enter to press button, Esc to exit focus mode.') + def _handle_mouse_press(self, x, y, mouse_event): + + if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK: + self.command() + + def _handle_key_press(self, key_pressed): """Override of base class, adds ENTER listener that runs the button's command @@ -828,7 +834,7 @@ def update_height_width(self): self._viewport_height = self._cursor_max_down - self._cursor_max_up - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Override of base class function, handles mouse press in menu Parameters @@ -837,27 +843,29 @@ def _handle_mouse_press(self, x, y): Coordinates of mouse press """ - super()._handle_mouse_press(x, y) - if y >= self._cursor_max_up and y <= self._cursor_max_down: - if x >= self._cursor_max_left and x <= self._cursor_max_right: - line_clicked_index = y - self._cursor_max_up + self._viewport_y_start - if len(self._text_lines) <= line_clicked_index: - self._cursor_text_pos_y = len(self._text_lines) - 1 - self._cursor_y = self._cursor_max_up + self._cursor_text_pos_y - self._viewport_y_start - line = self._text_lines[len(self._text_lines) - 1] - else: - self._cursor_text_pos_y = line_clicked_index - self._cursor_y = y - line = self._text_lines[line_clicked_index] - - if x <= len(line) + self._cursor_max_left: - old_text_pos = self._cursor_text_pos_x - old_cursor_x = self._cursor_x - self._cursor_x = x - self._cursor_text_pos_x = old_text_pos + (x - old_cursor_x) - else: - self._cursor_x = self._cursor_max_left + len(line) - self._cursor_text_pos_x = len(line) + super()._handle_mouse_press(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: + line_clicked_index = y - self._cursor_max_up + self._viewport_y_start + if len(self._text_lines) <= line_clicked_index: + self._cursor_text_pos_y = len(self._text_lines) - 1 + self._cursor_y = self._cursor_max_up + self._cursor_text_pos_y - self._viewport_y_start + line = self._text_lines[len(self._text_lines) - 1] + else: + self._cursor_text_pos_y = line_clicked_index + self._cursor_y = y + line = self._text_lines[line_clicked_index] + + if x <= len(line) + self._cursor_max_left: + old_text_pos = self._cursor_text_pos_x + old_cursor_x = self._cursor_x + self._cursor_x = x + self._cursor_text_pos_x = old_text_pos + (x - old_cursor_x) + else: + self._cursor_x = self._cursor_max_left + len(line) + self._cursor_text_pos_x = len(line) def _handle_key_press(self, key_pressed): From ce4097c91b31b55a04d49aded3fcc31dec7ef6cb Mon Sep 17 00:00:00 2001 From: jwlodek Date: Tue, 8 Jun 2021 17:04:39 -0400 Subject: [PATCH 19/40] Have all mouse events registering correctly now --- examples/mouse.py | 44 +++++++++++++++++++++++++++++++++++++ py_cui/__init__.py | 1 - py_cui/keys.py | 10 ++++----- py_cui/widgets.py | 54 +++++++++++++++++++++++++++++++++++++--------- 4 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 examples/mouse.py diff --git a/examples/mouse.py b/examples/mouse.py new file mode 100644 index 0000000..567f2c6 --- /dev/null +++ b/examples/mouse.py @@ -0,0 +1,44 @@ +import py_cui +import py_cui.keys as KEYS + + +class MouseApp: + + def __init__(self, root: py_cui.PyCUI): + + # Initialize our two widgets, a button and a mous press log + self.root = root + self.button_presser = self.root.add_button('Press Me!', 1, 0) + self.mouse_press_log = self.root.add_text_block('Mouse Presses', 0, 1, row_span=3, column_span=2) + + # This demonstrates how you can get mouse press coordinates to a mouse press event function + # You may pass in either a no-parameter function or one with two parameters as the second argument + # to add_mouse_command. If it is detected as one with two parameters, the function will be ran with + # the x, y coordinates of the press in terminal characters as the input. + self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_CLICK, self.print_left_press_with_coords) + + # Demonstration of how to add mouse commands for remaining left click events + self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_DBL_CLICK, lambda: self.mouse_press_log.set_text('Left Double\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_PRESSED, lambda: self.mouse_press_log.set_text('Left Pressed\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_RELEASED, lambda: self.mouse_press_log.set_text('Left Released\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Left Triple\n' + self.mouse_press_log.get())) + + # Demonstration of how to add mouse commands for remaining right click events + self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_CLICK, lambda: self.mouse_press_log.set_text('Right Single\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_DBL_CLICK, lambda: self.mouse_press_log.set_text('Right Double\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_PRESSED, lambda: self.mouse_press_log.set_text('Right Pressed\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_RELEASED, lambda: self.mouse_press_log.set_text('Right Released\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Right Triple\n' + self.mouse_press_log.get())) + + + + + def print_left_press_with_coords(self, x, y): + + self.mouse_press_log.set_text(f'Left Single w/ coords {x}, {y}\n' + self.mouse_press_log.get()) + + + +root = py_cui.PyCUI(3, 3) +MouseApp(root) +root.start() \ No newline at end of file diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 4b554be..62bef49 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1626,7 +1626,6 @@ def _draw(self, stdscr): self._logger.info('Detected mouse click') id, x, y, _, mouse_event = curses.getmouse() in_element = self.get_element_at_position(x, y) - self._logger.debug(f'In element : {in_element.get_title()}') # In first case, we click inside already selected widget, pass click for processing if in_element is not None: diff --git a/py_cui/keys.py b/py_cui/keys.py index 642b674..63ec192 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -262,11 +262,11 @@ def get_char_from_ascii(key_num): LEFT_MOUSE_PRESSED = curses.BUTTON1_PRESSED LEFT_MOUSE_RELEASED = curses.BUTTON1_RELEASED -RIGHT_MOUSE_CLICK = curses.BUTTON2_CLICKED -RIGHT_MOUSE_DBL_CLICK = curses.BUTTON2_DOUBLE_CLICKED -RIGHT_MOUSE_TRPL_CLICK = curses.BUTTON2_TRIPLE_CLICKED -RIGHT_MOUSE_PRESSED = curses.BUTTON2_PRESSED -RIGHT_MOUSE_RELEASED = curses.BUTTON2_RELEASED +RIGHT_MOUSE_CLICK = curses.BUTTON3_CLICKED +RIGHT_MOUSE_DBL_CLICK = curses.BUTTON3_DOUBLE_CLICKED +RIGHT_MOUSE_TRPL_CLICK = curses.BUTTON3_TRIPLE_CLICKED +RIGHT_MOUSE_PRESSED = curses.BUTTON3_PRESSED +RIGHT_MOUSE_RELEASED = curses.BUTTON3_RELEASED MOUSE_EVENTS = [LEFT_MOUSE_CLICK, LEFT_MOUSE_DBL_CLICK, LEFT_MOUSE_TRPL_CLICK, LEFT_MOUSE_PRESSED, LEFT_MOUSE_RELEASED, RIGHT_MOUSE_CLICK, RIGHT_MOUSE_DBL_CLICK, RIGHT_MOUSE_TRPL_CLICK, RIGHT_MOUSE_PRESSED, RIGHT_MOUSE_RELEASED] diff --git a/py_cui/widgets.py b/py_cui/widgets.py index dbc1115..713a73b 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -22,6 +22,7 @@ import curses +import inspect from typing import Callable import py_cui import py_cui.ui @@ -290,6 +291,40 @@ def _is_row_col_inside(self, row, col): # BELOW FUNCTIONS SHOULD BE OVERWRITTEN BY SUB-CLASSES + def _handle_mouse_press(self, x, y, mouse_event): + """Base class function that handles all assigned mouse presses. + + When overwriting this function, make sure to add a super()._handle_mouse_press(x, y, mouse_event) call, + as this is required for user defined key command support + + Parameters + ---------- + key_pressed : int + key code of key pressed + """ + + # 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 + try: + num_args = len(inspect.signature(command).parameters) + except ValueError: + self._logger.error('Failed to get mouse press command signature!') + except TypeError: + self._logger.error('Type of object not supported for signature identification!') + + # Depending on the number of parameters for the command, pass in the x and y + # values, or do nothing + if num_args == 2: + command(x, y) + else: + command() + + def _handle_key_press(self, key_pressed): """Base class function that handles all assigned key presses. @@ -478,7 +513,7 @@ def _handle_mouse_press(self, x, y, mouse_event): # For scroll menu, handle custom mouse press after initial event, since we will likely want to # have access to the newly selected item - super()._handle_mouse_press(x, y, mouse_event) + Widget._handle_mouse_press(x, y, mouse_event) @@ -518,7 +553,7 @@ def _draw(self): """Overrides base class draw function """ - super()._draw() + Widget._draw(self) self._renderer.set_color_mode(self._color) self._renderer.draw_border(self) counter = self._pady + 1 @@ -560,7 +595,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.set_help_text('Focus mode on CheckBoxMenu. Use up/down to scroll, Enter to toggle set, unset, Esc to exit.') - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Override of base class function, handles mouse press in menu Parameters @@ -569,7 +604,7 @@ def _handle_mouse_press(self, x, y): Coordinates of mouse press """ - super()._handle_mouse_press(x, y) + Widget._handle_mouse_press(self, x, y, mouse_event) viewport_top = self._start_y + self._pady + 1 if viewport_top <= y and viewport_top + len(self._view_items) - self._top_view >= y: elem_clicked = y - viewport_top + self._top_view @@ -656,12 +691,11 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa 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.') - - - def _handle_mouse_press(self, x, y, mouse_event): - if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK: - self.command() + # 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) + self.add_mouse_command(py_cui.keys.LEFT_MOUSE_DBL_CLICK, self.command) def _handle_key_press(self, key_pressed): @@ -856,7 +890,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self._cursor_text_pos_y = line_clicked_index self._cursor_y = y line = self._text_lines[line_clicked_index] - + if x <= len(line) + self._cursor_max_left: old_text_pos = self._cursor_text_pos_x old_cursor_x = self._cursor_x From 63ee0d4196040fdc8a5f18f017272eba1282d8a6 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Thu, 10 Jun 2021 12:11:36 -0400 Subject: [PATCH 20/40] Add middle mouse key codes, add drag/drop to example, catch errors --- examples/mouse.py | 32 ++++++++++++++++++++++++++++++-- py_cui/__init__.py | 30 +++++++++++++++++++----------- py_cui/keys.py | 7 +++++++ py_cui/widgets.py | 6 +++--- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/examples/mouse.py b/examples/mouse.py index 567f2c6..b086dff 100644 --- a/examples/mouse.py +++ b/examples/mouse.py @@ -8,8 +8,8 @@ def __init__(self, root: py_cui.PyCUI): # Initialize our two widgets, a button and a mous press log self.root = root - self.button_presser = self.root.add_button('Press Me!', 1, 0) - self.mouse_press_log = self.root.add_text_block('Mouse Presses', 0, 1, row_span=3, column_span=2) + self.button_presser = self.root.add_button('Press Me!', 0, 0) + self.mouse_press_log = self.root.add_text_block('Mouse Presses', 1, 0, row_span=2) # This demonstrates how you can get mouse press coordinates to a mouse press event function # You may pass in either a no-parameter function or one with two parameters as the second argument @@ -23,6 +23,13 @@ def __init__(self, root: py_cui.PyCUI): self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_RELEASED, lambda: self.mouse_press_log.set_text('Left Released\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Left Triple\n' + self.mouse_press_log.get())) + + self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_CLICK, lambda: self.mouse_press_log.set_text('Middle Single\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_DBL_CLICK, lambda: self.mouse_press_log.set_text('Middle Double\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_PRESSED, lambda: self.mouse_press_log.set_text('Middle Pressed\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_RELEASED, lambda: self.mouse_press_log.set_text('Middle Released\n' + self.mouse_press_log.get())) + self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Middle Triple\n' + self.mouse_press_log.get())) + # Demonstration of how to add mouse commands for remaining right click events self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_CLICK, lambda: self.mouse_press_log.set_text('Right Single\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_DBL_CLICK, lambda: self.mouse_press_log.set_text('Right Double\n' + self.mouse_press_log.get())) @@ -31,6 +38,27 @@ def __init__(self, root: py_cui.PyCUI): self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Right Triple\n' + self.mouse_press_log.get())) + # Two scroll menus demonstrating how to handle drag and drop functionality with py_cui + self.drag_from = self.root.add_scroll_menu('Drag From', 0, 1, row_span=3) + self.drag_to = self.root.add_scroll_menu('Drag To', 0, 2, row_span=3) + + self.drag_from.add_mouse_command(KEYS.LEFT_MOUSE_PRESSED, self.drag) + self.drag_to.add_mouse_command(KEYS.LEFT_MOUSE_RELEASED, self.drop) + + self.drag_from.add_item_list(['Drag Me', 'Drop Me Too']) + + self.current_clipboard_elem = None + + + + def drag(self): + self.current_clipboard_elem = self.drag_from.get() + + + def drop(self): + self.drag_to.add_item(self.current_clipboard_elem) + self.drag_from.remove_item(self.current_clipboard_elem) + self.current_clipboard_elem = None def print_left_press_with_coords(self, x, y): diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 62bef49..203199c 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1624,18 +1624,26 @@ def _draw(self, stdscr): # 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') - id, x, y, _, mouse_event = curses.getmouse() - in_element = self.get_element_at_position(x, y) - - # 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()}') - 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): - self.move_focus(in_element) - in_element._handle_mouse_press(x, y, mouse_event) + 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)}') + + 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 + if in_element is not None: + 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 + 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: diff --git a/py_cui/keys.py b/py_cui/keys.py index 63ec192..f831d44 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -262,6 +262,12 @@ def get_char_from_ascii(key_num): LEFT_MOUSE_PRESSED = curses.BUTTON1_PRESSED LEFT_MOUSE_RELEASED = curses.BUTTON1_RELEASED +MIDDLE_MOUSE_CLICK = curses.BUTTON2_CLICKED +MIDDLE_MOUSE_DBL_CLICK = curses.BUTTON2_DOUBLE_CLICKED +MIDDLE_MOUSE_TRPL_CLICK = curses.BUTTON2_TRIPLE_CLICKED +MIDDLE_MOUSE_PRESSED = curses.BUTTON2_PRESSED +MIDDLE_MOUSE_RELEASED = curses.BUTTON2_RELEASED + RIGHT_MOUSE_CLICK = curses.BUTTON3_CLICKED RIGHT_MOUSE_DBL_CLICK = curses.BUTTON3_DOUBLE_CLICKED RIGHT_MOUSE_TRPL_CLICK = curses.BUTTON3_TRIPLE_CLICKED @@ -269,6 +275,7 @@ def get_char_from_ascii(key_num): RIGHT_MOUSE_RELEASED = curses.BUTTON3_RELEASED MOUSE_EVENTS = [LEFT_MOUSE_CLICK, LEFT_MOUSE_DBL_CLICK, LEFT_MOUSE_TRPL_CLICK, LEFT_MOUSE_PRESSED, LEFT_MOUSE_RELEASED, + MIDDLE_MOUSE_CLICK, MIDDLE_MOUSE_DBL_CLICK, MIDDLE_MOUSE_TRPL_CLICK, MIDDLE_MOUSE_PRESSED, MIDDLE_MOUSE_RELEASED, RIGHT_MOUSE_CLICK, RIGHT_MOUSE_DBL_CLICK, RIGHT_MOUSE_TRPL_CLICK, RIGHT_MOUSE_PRESSED, RIGHT_MOUSE_RELEASED] # Pressing backspace returns 8 on windows? diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 713a73b..d7a940c 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -513,7 +513,7 @@ def _handle_mouse_press(self, x, y, mouse_event): # For scroll menu, handle custom mouse press after initial event, since we will likely want to # have access to the newly selected item - Widget._handle_mouse_press(x, y, mouse_event) + Widget._handle_mouse_press(self, x, y, mouse_event) @@ -758,7 +758,7 @@ def update_height_width(self): self._viewport_width = self._cursor_max_right - self._cursor_max_left - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Override of base class function, handles mouse press in menu Parameters @@ -767,7 +767,7 @@ def _handle_mouse_press(self, x, y): Coordinates of mouse press """ - super()._handle_mouse_press(x, y) + super()._handle_mouse_press(x, y, mouse_event) if y == self._cursor_y and x >= self._cursor_max_left and x <= self._cursor_max_right: if x <= len(self._text) + self._cursor_max_left: old_text_pos = self._cursor_text_pos From 239038b5e83f011d1e39f8cf8b26ca303cfec2da Mon Sep 17 00:00:00 2001 From: jwlodek Date: Thu, 10 Jun 2021 12:54:22 -0400 Subject: [PATCH 21/40] Add mouse press support to textbox and menu popups --- py_cui/popups.py | 48 +++++++++++++++++++++++++++++++++++++++++++---- py_cui/widgets.py | 17 ++++++++--------- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/py_cui/popups.py b/py_cui/popups.py index 2e0fde2..6aea6e6 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -191,7 +191,7 @@ def _handle_key_press(self, key_pressed): if self._command is not None: self._command(ret_val) else: - self._root.show_warning_popup('No Command Specified', 'The textbox popup had no specified command!') + self._root.show_warning_popup('No Command Specified', 'The yes/no popup had no specified command!') def _draw(self): @@ -224,7 +224,7 @@ def update_height_width(self): """Need to update all cursor positions on resize """ - super().update_height_width() + Popup.update_height_width(self) padx, pady = self.get_padding() start_x, start_y = self.get_start_position() height, width = self.get_absolute_dimensions() @@ -236,6 +236,27 @@ def update_height_width(self): self._viewport_width = self._cursor_max_right - self._cursor_max_left + def _handle_mouse_press(self, x, y, mouse_event): + """Override of base class function, handles mouse press in menu + + Parameters + ---------- + x, y : int + Coordinates of mouse press + """ + + Popup._handle_mouse_press(self, x, y, mouse_event) + if y == self._cursor_y and x >= self._cursor_max_left and x <= self._cursor_max_right: + if x <= len(self._text) + self._cursor_max_left: + old_text_pos = self._cursor_text_pos + old_cursor_x = self._cursor_x + self._cursor_x = x + self._cursor_text_pos = old_text_pos + (x - old_cursor_x) + else: + self._cursor_x = self._cursor_max_left + len(self._text) + self._cursor_text_pos = len(self._text) + + def _handle_key_press(self, key_pressed): """Override of base handle key press function @@ -245,7 +266,7 @@ def _handle_key_press(self, key_pressed): key code of key pressed """ - super()._handle_key_press(key_pressed) + Popup._handle_key_press(self, key_pressed) valid_pressed = False if key_pressed == py_cui.keys.KEY_ENTER: self._ret_val = self._text @@ -325,6 +346,25 @@ def __init__(self, root, items, title, color, command, renderer, logger, run_com self._run_command_if_none = run_command_if_none + def _handle_mouse_press(self, x, y, mouse_event): + """Override of base class function, handles mouse press in menu + + Parameters + ---------- + x, y : int + Coordinates of mouse press + """ + + # For either click or double click we want to jump to the clicked-on item + if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK or mouse_event == py_cui.keys.LEFT_MOUSE_DBL_CLICK: + current = self.get_selected_item_index() + viewport_top = self._start_y + self._pady + 1 + + 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) + + def _handle_key_press(self, key_pressed): """Override of base handle key press function @@ -336,7 +376,7 @@ def _handle_key_press(self, key_pressed): key code of key pressed """ - super()._handle_key_press(key_pressed) + Popup._handle_key_press(self, key_pressed) valid_pressed = False if key_pressed == py_cui.keys.KEY_ENTER: ret_val = self.get() diff --git a/py_cui/widgets.py b/py_cui/widgets.py index d7a940c..fd606b8 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -94,7 +94,6 @@ def add_key_command(self, key, command): a non-argument function or lambda function to execute if in focus mode and key is pressed """ - self._key_commands[key] = command @@ -528,7 +527,7 @@ def _handle_key_press(self, key_pressed): key code of key pressed """ - super()._handle_key_press(key_pressed) + Widget._handle_key_press(self, key_pressed) current = self.get_selected_item_index() viewport_height = self.get_viewport_height() @@ -745,7 +744,7 @@ def update_height_width(self): """Need to update all cursor positions on resize """ - super().update_height_width() + Widget.update_height_width(self) padx, _ = self.get_padding() start_x, start_y = self.get_start_position() height, width = self.get_absolute_dimensions() @@ -767,7 +766,7 @@ def _handle_mouse_press(self, x, y, mouse_event): Coordinates of mouse press """ - super()._handle_mouse_press(x, y, mouse_event) + Widget._handle_mouse_press(self, x, y, mouse_event) if y == self._cursor_y and x >= self._cursor_max_left and x <= self._cursor_max_right: if x <= len(self._text) + self._cursor_max_left: old_text_pos = self._cursor_text_pos @@ -788,7 +787,7 @@ def _handle_key_press(self, key_pressed): key code of key pressed """ - super()._handle_key_press(key_pressed) + Widget._handle_key_press(self, key_pressed) if key_pressed == py_cui.keys.KEY_LEFT_ARROW: self._move_left() elif key_pressed == py_cui.keys.KEY_RIGHT_ARROW: @@ -810,7 +809,7 @@ def _draw(self): """Override of base draw function """ - super()._draw() + Widget._draw(self) self._renderer.set_color_mode(self._color) self._renderer.draw_text(self, self._title, self._cursor_y - 2, bordered=False) @@ -876,7 +875,7 @@ def _handle_mouse_press(self, x, y, mouse_event): Coordinates of mouse press """ - super()._handle_mouse_press(x, y, mouse_event) + 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: @@ -910,7 +909,7 @@ def _handle_key_press(self, key_pressed): key code of key pressed """ - super()._handle_key_press(key_pressed) + Widget._handle_key_press(self, key_pressed) if key_pressed == py_cui.keys.KEY_LEFT_ARROW: self._move_left() @@ -942,7 +941,7 @@ def _draw(self): """Override of base class draw function """ - super()._draw() + Widget._draw(self) self._renderer.set_color_mode(self._color) self._renderer.draw_border(self) From 7e6a2e5e59c7ec9cdfd9539987fbf6781ef6aa7e Mon Sep 17 00:00:00 2001 From: jwlodek Date: Thu, 10 Jun 2021 12:59:14 -0400 Subject: [PATCH 22/40] Execute menu popups when item is double clicked --- py_cui/popups.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py_cui/popups.py b/py_cui/popups.py index 6aea6e6..097faaf 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -364,6 +364,13 @@ def _handle_mouse_press(self, x, y, mouse_event): elem_clicked = y - viewport_top + self._top_view self.set_selected_item_index(elem_clicked) + # For double clicks we also process the menu selection + if mouse_event == py_cui.keys.LEFT_MOUSE_DBL_CLICK: + ret_val = self.get() + self.root.close_popup() + self._command(ret_val) + + def _handle_key_press(self, key_pressed): """Override of base handle key press function From 06e3eacaa843ca6fb24384c652a41a5300695365 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Thu, 10 Jun 2021 13:21:27 -0400 Subject: [PATCH 23/40] Fixing _handle_mouse_press type signatures, clean up key-code module --- py_cui/debug.py | 15 ++++++---- py_cui/dialogs/filedialog.py | 36 ++++++++++++++---------- py_cui/dialogs/form.py | 4 +-- py_cui/keys.py | 54 ++++++++++++++++++++++++------------ 4 files changed, 70 insertions(+), 39 deletions(-) diff --git a/py_cui/debug.py b/py_cui/debug.py index 85c40b0..64c0349 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -166,20 +166,23 @@ def get_absolute_stop_pos(self): return stop_x, stop_y - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Override of base class function, handles mouse press in menu Parameters ---------- x, y : int Coordinates of mouse press + mouse_event : int + Key code for py_cui mouse event """ - super()._handle_mouse_press(x, y) - viewport_top = self._start_y + self._pady + 1 - 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) + py_cui.ui.UIElement._handle_mouse_press(x, y, mouse_event) + if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK: + viewport_top = self._start_y + self._pady + 1 + 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) def _handle_key_press(self, key_pressed): diff --git a/py_cui/dialogs/filedialog.py b/py_cui/dialogs/filedialog.py index 45c6aa2..5edfcaa 100644 --- a/py_cui/dialogs/filedialog.py +++ b/py_cui/dialogs/filedialog.py @@ -190,10 +190,14 @@ def __init__(self, root, initial_dir, dialog_type, ascii_icons, title, color, co self._command = command self._parent_dialog = root self._text_color_rules = [ - py_cui.colors.ColorRule('\U0001f4c1', py_cui.BLUE_ON_BLACK, py_cui.WHITE_ON_BLUE,'startswith', 'region', [3, 1000], False, logger), - py_cui.colors.ColorRule('', py_cui.BLUE_ON_BLACK, py_cui.WHITE_ON_BLUE, 'startswith', 'region', [6, 1000], False, logger), - py_cui.colors.ColorRule(' ', py_cui.WHITE_ON_BLACK, py_cui.BLACK_ON_WHITE, 'startswith', 'region', [6, 1000], True, logger), - py_cui.colors.ColorRule('\U0001f5ce', py_cui.WHITE_ON_BLACK, py_cui.BLACK_ON_WHITE, 'startswith', 'region', [3, 1000], False, logger) + py_cui.colors.ColorRule('\U0001f4c1', py_cui.BLUE_ON_BLACK, py_cui.WHITE_ON_BLUE, + 'startswith', 'region', [3, 1000], False, logger), + py_cui.colors.ColorRule('', py_cui.BLUE_ON_BLACK, py_cui.WHITE_ON_BLUE, + 'startswith', 'region', [6, 1000], False, logger), + py_cui.colors.ColorRule(' ', py_cui.WHITE_ON_BLACK, py_cui.BLACK_ON_WHITE, + 'startswith', 'region', [6, 1000], True, logger), + py_cui.colors.ColorRule('\U0001f5ce', py_cui.WHITE_ON_BLACK, py_cui.BLACK_ON_WHITE, + 'startswith', 'region', [3, 1000], False, logger) ] #self._run_command_if_none = run_command_if_none @@ -363,7 +367,7 @@ def update_height_width(self): """Override of base class. Updates text field variables for form field """ - super().update_height_width() + py_cui.popups.Popup.update_height_width(self) padx, pady = self.get_padding() start_x, start_y = self.get_start_position() height, width = self.get_absolute_dimensions() @@ -502,7 +506,7 @@ def get_absolute_stop_pos(self): return stop_x, stop_y - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Handles mouse presses Parameters @@ -511,10 +515,13 @@ def _handle_mouse_press(self, x, y): x coordinate of click in characters y : int y coordinate of click in characters + mouse_event : int + Mouse event keycode of mouse press """ - super()._handle_mouse_press(x, y) - self.perform_command() + super()._handle_mouse_press(x, y, mouse_event) + if mouse_event == py_cui.keys.LEFT_MOUSE_CLICK: + self.perform_command() def _handle_key_press(self, key_pressed): @@ -777,7 +784,7 @@ def _handle_key_press(self, key_pressed): self._internal_popup._handle_key_press(key_pressed) - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Override of base class function Simply enters the appropriate field when mouse is pressed on it @@ -788,21 +795,22 @@ def _handle_mouse_press(self, x, y): Coordinates of the mouse press """ - super()._handle_mouse_press(x, y) + super()._handle_mouse_press(x, y, mouse_event) if self._file_dir_select._contains_position(x, y): self._filename_input.set_selected(False) self._file_dir_select.set_selected(True) - self._file_dir_select._handle_mouse_press(x, y) + 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) + 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) + self._submit_button._handle_mouse_press(x, y, mouse_event) + elif self._cancel_button._contains_position(x, y): - self._cancel_button._handle_mouse_press(x, y) + self._cancel_button._handle_mouse_press(x, y, mouse_event) def _draw(self): diff --git a/py_cui/dialogs/form.py b/py_cui/dialogs/form.py index 41ea09f..197f98f 100644 --- a/py_cui/dialogs/form.py +++ b/py_cui/dialogs/form.py @@ -495,7 +495,7 @@ def _handle_key_press(self, key_pressed): self._internal_popup._handle_key_press(key_pressed) - def _handle_mouse_press(self, x, y): + def _handle_mouse_press(self, x, y, mouse_event): """Override of base class function Simply enters the appropriate field when mouse is pressed on it @@ -506,7 +506,7 @@ def _handle_mouse_press(self, x, y): Coordinates of the mouse press """ - super()._handle_mouse_press(x, y) + py_cui.popups.Popup._handle_mouse_press(self, x, y, mouse_event) for i, field in enumerate(self._form_fields): if field._contains_position(x, y): self._form_fields[self.get_selected_form_index()].set_selected(False) diff --git a/py_cui/keys.py b/py_cui/keys.py index f831d44..58ffb1b 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -55,6 +55,24 @@ def get_char_from_ascii(key_num): 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] + + +# Page navigation keys +KEY_PAGE_UP = curses.KEY_PPAGE +KEY_PAGE_DOWN = curses.KEY_NPAGE +KEY_HOME = curses.KEY_HOME +KEY_END = curses.KEY_END + # Arrow Keys KEY_UP_ARROW = curses.KEY_UP KEY_DOWN_ARROW = curses.KEY_DOWN @@ -62,6 +80,9 @@ def get_char_from_ascii(key_num): KEY_RIGHT_ARROW = curses.KEY_RIGHT +ARROW_KEYS = [KEY_UP_ARROW, KEY_DOWN_ARROW, KEY_LEFT_ARROW, KEY_RIGHT_ARROW] + + if platform == 'linux' or platform == 'darwin': # Modified arrow keys @@ -88,14 +109,8 @@ def get_char_from_ascii(key_num): KEY_CTRL_DOWN = 481 -ARROW_KEYS = [KEY_UP_ARROW, KEY_DOWN_ARROW, KEY_LEFT_ARROW, KEY_RIGHT_ARROW] - - -# Page navigation keys -KEY_PAGE_UP = curses.KEY_PPAGE -KEY_PAGE_DOWN = curses.KEY_NPAGE -KEY_HOME = curses.KEY_HOME -KEY_END = curses.KEY_END +MODIFIED_ARROW_KEYS = [KEY_SHIFT_LEFT, KEY_SHIFT_RIGHT, KEY_SHIFT_UP, KEY_SHIFT_DOWN, + KEY_CTRL_LEFT, KEY_CTRL_RIGHT, KEY_CTRL_UP, KEY_CTRL_DOWN] # Standard letter keys @@ -161,7 +176,7 @@ def get_char_from_ascii(key_num): LETTER_KEYS = UPPERCASE_LETTER_KEYS + LOWERCASE_LETTER_KEYS -# Control Keys +# Control-modified Keys KEY_CTRL_A = 1 KEY_CTRL_B = 2 KEY_CTRL_C = 3 @@ -223,6 +238,11 @@ def get_char_from_ascii(key_num): KEY_ALT_Y = 121 + _ALT_MODIFIER KEY_ALT_Z = 122 + _ALT_MODIFIER +ALT_MODIFIED_LETTERS = [_alt_mod_key + _ALT_MODIFIER for _alt_mod_key in range(97, 123)] + +# List of all ctrl / alt modified key codes supported by py_cui +MODIFIED_LETTER_KEYS = CTRL_MODIFIED_LETTERS + ALT_MODIFIED_LETTERS + # Function keys KEY_F0 = curses.KEY_F0 KEY_F1 = curses.KEY_F1 @@ -238,6 +258,9 @@ def get_char_from_ascii(key_num): KEY_F11 = curses.KEY_F11 KEY_F12 = curses.KEY_F12 +FUNCTION_KEYS = [KEY_F0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12] + # Number keys KEY_0 = get_ascii_from_char('0') @@ -255,6 +278,11 @@ def get_char_from_ascii(key_num): NUMBER_KEYS = [KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9] ALPHANUMERIC_KEYS = LETTER_KEYS + NUMBER_KEYS + +# List of all key events supported by py_cui +ALL_KEYS = ALPHANUMERIC_KEYS + FUNCTION_KEYS + MODIFIED_LETTER_KEYS + SPECIAL_KEYS + ARROW_KEYS + MODIFIED_ARROW_KEYS + + # Mouse click events LEFT_MOUSE_CLICK = curses.BUTTON1_CLICKED LEFT_MOUSE_DBL_CLICK = curses.BUTTON1_DOUBLE_CLICKED @@ -278,12 +306,4 @@ def get_char_from_ascii(key_num): MIDDLE_MOUSE_CLICK, MIDDLE_MOUSE_DBL_CLICK, MIDDLE_MOUSE_TRPL_CLICK, MIDDLE_MOUSE_PRESSED, MIDDLE_MOUSE_RELEASED, RIGHT_MOUSE_CLICK, RIGHT_MOUSE_DBL_CLICK, RIGHT_MOUSE_TRPL_CLICK, RIGHT_MOUSE_PRESSED, RIGHT_MOUSE_RELEASED] -# 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 From 4c2352ae5631779bf9c4f39148757e17357e9e94 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Thu, 10 Jun 2021 13:32:16 -0400 Subject: [PATCH 24/40] Update gitignore, fix mark_item_as_checked function misnomer --- .gitignore | 3 ++- py_cui/ui.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c920666..2414124 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ ecui/ py_cui_log.txt venv/ .pytest_cache/ -demo.py \ No newline at end of file +demo.py +*.log \ No newline at end of file diff --git a/py_cui/ui.py b/py_cui/ui.py index b63b6d7..20c5375 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -956,18 +956,42 @@ def remove_item(self, item): super().remove_item(item) - def mark_item_as_checked(self, item): + def toggle_item_checked(self, item): """Function that marks an item as selected Parameters ---------- item : object - Mark item as checked + Toggle item checked state """ self._selected_item_dict[item] = not self._selected_item_dict[item] + def mark_item_as_checked(self, item): + """Function that marks an item as selected + + Parameters + ---------- + item : object + Toggle item checked state + """ + + self._selected_item_dict[item] = True + + + def mark_item_as_not_checked(self, item): + """Function that marks an item as selected + + Parameters + ---------- + item : object + Item to uncheck + """ + + self._selected_item_dict[item] = False + + class TextBlockImplementation(UIImplementation): """Base class for TextBlockImplementation From ef26cb0c9e3d256cd1ad73d5cb5414ca46b3c129 Mon Sep 17 00:00:00 2001 From: mohan-cloud Date: Wed, 16 Jun 2021 23:48:23 +0530 Subject: [PATCH 25/40] Add type annotations to __init__.py --- py_cui/__init__.py | 277 ++++++++++++++++++++++++--------------------- 1 file changed, 148 insertions(+), 129 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 203199c..091e498 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 # py_cui imports import py_cui @@ -43,7 +43,7 @@ __version__ = '0.1.4' -def fit_text(width, text, center=False): +def fit_text(width: int, text: str, center: bool=False) -> str: """Fits text to screen size Helper function to fit text within a given width. Used to fix issue with status/title bar text @@ -108,8 +108,8 @@ class PyCUI: Dimensions for an alternative simulated terminal (used for testing) """ - def __init__(self, num_rows, num_cols, auto_focus_buttons=True, - exit_key=py_cui.keys.KEY_Q_LOWER, simulated_terminal=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 """ @@ -128,8 +128,8 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, height = term_size.lines width = term_size.columns else: - height = simulated_terminal[0] - width = simulated_terminal[1] + height = self._simulated_terminal[0] + 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) @@ -158,36 +158,36 @@ def __init__(self, num_rows, num_cols, auto_focus_buttons=True, # Initialize grid, renderer, and widget dict self._grid = py_cui.grid.Grid(num_rows, num_cols, self._height, self._width, self._logger) - self._renderer = None - self._border_characters = None - self._stdscr = None - self._widgets = {} + 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 = None + self._selected_widget: Optional[int] = None self._in_focused_mode = False - self._popup = None + self._popup: Any = None self._auto_focus_buttons = auto_focus_buttons # CUI blocks when loading popup is open self._loading = False self._stopped = False - self._post_loading_callback = None - self._on_draw_update_func = None + 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 = {} + self._keybindings: Dict[int,Any] = {} # to be discussed 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 = None + self._toggle_live_debug_key: Optional[int] = None # Callback to fire when CUI is stopped. - self._on_stop = None + self._on_stop: Optional[Callable[[],Any]] = None - def set_refresh_timeout(self, timeout): + def set_refresh_timeout(self, timeout: int): """Sets the CUI auto-refresh timeout to a number of seconds. Parameters @@ -200,7 +200,7 @@ def set_refresh_timeout(self, timeout): self._refresh_timeout = int(timeout * 1000) - def set_on_draw_update_func(self, update_function): + 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 @@ -212,7 +212,7 @@ def set_on_draw_update_func(self, update_function): self._on_draw_update_func = update_function - def set_widget_cycle_key(self, forward_cycle_key=None, reverse_cycle_key=None): + def set_widget_cycle_key(self, forward_cycle_key: int=None, reverse_cycle_key: int=None) -> None: # Keycodes are type hinted as int """Assigns a key for automatically cycling through widgets in both focus and overview modes Parameters @@ -227,11 +227,11 @@ def set_widget_cycle_key(self, forward_cycle_key=None, reverse_cycle_key=None): self._reverse_cycle_key = reverse_cycle_key - def set_toggle_live_debug_key(self, toggle_debug_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='py_cui.log', logging_level = logging.DEBUG, live_debug_key = py_cui.keys.KEY_CTRL_D): + 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 @@ -250,7 +250,7 @@ def enable_logging(self, log_file_path='py_cui.log', logging_level = logging.DEB print(f'Failed to initialize logger: {str(e)}') - def apply_widget_set(self, new_widget_set): + 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 @@ -278,7 +278,7 @@ def apply_widget_set(self, new_widget_set): raise TypeError('Argument must be of type py_cui.widget_set.WidgetSet') - def create_new_widget_set(self, num_rows, num_cols): + 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 @@ -308,7 +308,7 @@ def create_new_widget_set(self, num_rows, num_cols): # ----------------------------------------------# - def start(self): + def start(self) -> None: """Function that starts the CUI """ @@ -316,7 +316,7 @@ def start(self): curses.wrapper(self._draw) - def stop(self): + def stop(self) -> None: """Function that stops the CUI, and fires the callback function. Callback must be a no arg method @@ -326,7 +326,7 @@ def stop(self): self._stopped = True - def run_on_exit(self, command): + def run_on_exit(self, command: Callable[[],Any]): """Sets callback function on CUI exit. Must be a no-argument function or lambda function Parameters @@ -338,7 +338,7 @@ def run_on_exit(self, command): self._on_stop = command - def set_title(self, title): + def set_title(self, title: str) -> None: """Sets the title bar text Parameters @@ -350,7 +350,7 @@ def set_title(self, title): self._title = title - def set_status_bar_text(self, text): + def set_status_bar_text(self, text: str) -> None: """Sets the status bar text when in overview mode Parameters @@ -363,7 +363,7 @@ def set_status_bar_text(self, text): self.status_bar.set_text(text) - def _initialize_colors(self): + def _initialize_colors(self) -> None: """Function for initialzing curses colors. Called when CUI is first created. """ @@ -375,16 +375,17 @@ def _initialize_colors(self): curses.init_pair(color_pair, fg_color, bg_color) - def _initialize_widget_renderer(self): + def _initialize_widget_renderer(self) -> None: """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) for widget_id in self.get_widgets().keys(): - if self.get_widgets()[widget_id] is not None: + widget = self.get_widgets()[widget_id] # using a temporary variable so that mypy doesn't show error + if widget is not None: try: - self.get_widgets()[widget_id]._assign_renderer(self._renderer) + widget._assign_renderer(self._renderer) except py_cui.errors.PyCUIError: self._logger.debug(f'Renderer already assigned for widget {self.get_widgets()[widget_id]}') try: @@ -396,7 +397,7 @@ def _initialize_widget_renderer(self): self._logger.debug('Renderer already assigned to popup or live-debug elements') - def toggle_unicode_borders(self): + def toggle_unicode_borders(self) -> None: """Function for toggling unicode based border rendering """ @@ -406,7 +407,7 @@ def toggle_unicode_borders(self): self.set_widget_border_characters('+', '+', '+', '+', '-', '|') - def set_widget_border_characters(self, upper_left_corner, upper_right_corner, lower_left_corner, lower_right_corner, horizontal, vertical): + 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,12 +437,12 @@ def set_widget_border_characters(self, upper_left_corner, upper_right_corner, lo self._logger.debug(f'Set border_characters to {self._border_characters}') - def get_widgets(self): + def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']]: # docstring to be updated? """Function that gets current set of widgets Returns ------- - widgets : dict of str -> widget + widgets : dict of str -> widget # dict of int -> widget dictionary mapping widget IDs to object instances """ @@ -450,7 +451,7 @@ def get_widgets(self): # 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, row, column, row_span=1, column_span=1, padx=1, pady=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 @@ -495,7 +496,7 @@ def add_scroll_menu(self, title, row, column, row_span=1, column_span=1, padx=1, return new_scroll_menu - def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx=1, pady=0, checked_char='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 @@ -543,7 +544,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= return new_checkbox_menu - def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = '', password = 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 @@ -592,7 +593,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = return new_text_box - def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = '') -> 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 @@ -640,7 +641,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx return new_text_block - def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 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 @@ -683,7 +684,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, return new_label - def add_block_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, center=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 @@ -729,7 +730,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad return new_label - def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, command=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 @@ -777,9 +778,9 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 return new_button - def add_slider(self, title, row, column, row_span=1, - column_span=1, padx=1, pady=0, - min_val=0, max_val=100, step=1, init_val=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 @@ -858,7 +859,7 @@ def forget_widget(self, widget : py_cui.widgets.Widget) -> None: self.get_widgets()[widget.get_id()] = None - def get_element_at_position(self, x, y): + def get_element_at_position(self, x: int, y: int) -> Optional['py_cui.ui.UIElement']: """Returns containing widget for character position Parameters @@ -879,13 +880,14 @@ def get_element_at_position(self, x, y): elif self._popup is None: for widget_id in self.get_widgets().keys(): - if self.get_widgets()[widget_id] is not None: - if self.get_widgets()[widget_id]._contains_position(x, y): - return self.get_widgets()[widget_id] + widget = self.get_widgets()[widget_id] # using temp variable for mypy + if widget is not None: + if widget._contains_position(x, y): + return widget return None - def _get_horizontal_neighbors(self, widget, direction): + def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]]: """Gets all horizontal (left, right) neighbor widgets Parameters @@ -919,8 +921,9 @@ def _get_horizontal_neighbors(self, 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(): - if self.get_widgets()[widget_id] is not None: - if self.get_widgets()[widget_id]._is_row_col_inside(row, col) and widget_id not in id_list: + 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: id_list.append(widget_id) if direction == py_cui.keys.KEY_LEFT_ARROW: @@ -932,7 +935,7 @@ def _get_horizontal_neighbors(self, widget, direction): return id_list - def _get_vertical_neighbors(self, widget, direction): + def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]]: """Gets all vertical (up, down) neighbor widgets Parameters @@ -966,8 +969,9 @@ def _get_vertical_neighbors(self, widget, direction): 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(): - if self.get_widgets()[widget_id] is not None: - if self.get_widgets()[widget_id]._is_row_col_inside(row, col) and widget_id not in id_list: + item_value = self.get_widgets()[widget_id] # using temp 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: id_list.append(widget_id) if direction == py_cui.keys.KEY_UP_ARROW: @@ -981,7 +985,7 @@ def _get_vertical_neighbors(self, widget, direction): # CUI status functions. Used to switch between widgets, set the mode, and # identify neighbors for overview mode - def _check_if_neighbor_exists(self, direction): + def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: """Function that checks if widget has neighbor in specified cell. Used for navigating CUI, as arrow keys find the immediate neighbor @@ -993,27 +997,29 @@ def _check_if_neighbor_exists(self, direction): Returns ------- - widget_id : str + widget_id : str # returns int after commit 936dee1 The widget neighbor ID if found, None otherwise """ - start_widget = self.get_widgets()[self._selected_widget] + if self._selected_widget is not None: + start_widget: Optional[py_cui.widgets.Widget] = self.get_widgets()[self._selected_widget] # Find all the widgets in the given row or column - neighbors = [] - 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 len(neighbors) == 0: + 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 neighbors is None: return None # We select the best match to jump to (first neighbor) return neighbors[0] - def get_selected_widget(self): + def get_selected_widget(self) -> Optional['py_cui.widgets.Widget']: """Function that gets currently selected widget Returns @@ -1029,12 +1035,12 @@ def get_selected_widget(self): return None - def set_selected_widget(self, widget_id): + def set_selected_widget(self, widget_id: int) -> None: """Function that sets the selected widget for the CUI Parameters ---------- - widget_id : str + widget_id : str #to be changed the id of the widget to select """ @@ -1045,7 +1051,7 @@ def set_selected_widget(self, widget_id): self._logger.warn(f'Widget w/ ID {widget_id} does not exist among current widgets.') - def lose_focus(self): + def lose_focus(self) -> None: """Function that forces py_cui out of focus mode. After popup is called, focus is lost @@ -1054,12 +1060,15 @@ def lose_focus(self): if self._in_focused_mode: self._in_focused_mode = False self.status_bar.set_text(self._init_status_bar_text) - self.get_widgets()[self._selected_widget].set_selected(False) + if self._selected_widget is not None: + widget = self.get_widgets()[self._selected_widget] + if widget is not None: + widget.set_selected(False) else: self._logger.info('lose_focus: Not currently in focus mode') - def move_focus(self, widget, auto_press_buttons=True): + def move_focus(self, widget: 'py_cui.widgets.Widget', auto_press_buttons: bool=True) -> None: """Moves focus mode to different widget Parameters @@ -1088,7 +1097,7 @@ def move_focus(self, widget, auto_press_buttons=True): self._logger.debug(f'Moved focus to widget {widget.get_title()}') - def _cycle_widgets(self, reverse=False): + def _cycle_widgets(self, reverse: bool=False) -> None: """Function that is fired if cycle key is pressed to move to next widget Parameters @@ -1098,34 +1107,37 @@ def _cycle_widgets(self, reverse=False): """ num_widgets = len(self.get_widgets().keys()) - current_widget_num = self._selected_widget - - 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 = current_widget_num - next_widget_id = next_widget_num - - if self._in_focused_mode and cycle_key in self.get_widgets()[current_widget_id]._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(self.get_widgets()[next_widget_id], auto_press_buttons=False) + 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) - def add_key_command(self, key, command): + def add_key_command(self, key: int, command: Callable[[],Any]) -> None: """Function that adds a keybinding to the CUI when in overview mode Parameters @@ -1140,7 +1152,7 @@ def add_key_command(self, key, command): # Popup functions. Used to display messages, warnings, and errors to the user. - def show_message_popup(self, title, text): + def show_message_popup(self, title: str, text: str) -> None: """Shows a message popup Parameters @@ -1156,7 +1168,7 @@ def show_message_popup(self, title, text): self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_warning_popup(self, title, text): + def show_warning_popup(self, title: str, text: str) -> None: """Shows a warning popup Parameters @@ -1172,7 +1184,7 @@ def show_warning_popup(self, title, text): self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_error_popup(self, title, text): + def show_error_popup(self, title: str, text: str) -> None: """Shows an error popup Parameters @@ -1188,7 +1200,7 @@ def show_error_popup(self, title, text): self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_yes_no_popup(self, title, command): + def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]): """Shows a yes/no popup. The 'command' parameter must be a function with a single boolean parameter @@ -1206,7 +1218,7 @@ def show_yes_no_popup(self, title, command): self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_text_box_popup(self, title, command, password=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 @@ -1226,7 +1238,7 @@ def show_text_box_popup(self, title, command, password=False): self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_menu_popup(self, title, menu_items, command, run_command_if_none=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 @@ -1248,7 +1260,7 @@ def show_menu_popup(self, title, menu_items, command, run_command_if_none=False) self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_loading_icon_popup(self, title, message, callback=None): + def show_loading_icon_popup(self, title: str, message: str, callback: Callable[[],Any]=None): """Shows a loading icon popup Parameters @@ -1271,7 +1283,7 @@ def show_loading_icon_popup(self, title, message, callback=None): self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_loading_bar_popup(self, title, num_items, callback=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 @@ -1296,7 +1308,7 @@ def show_loading_bar_popup(self, title, num_items, callback=None): self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_form_popup(self, title, fields, passwd_fields=[], required=[], callback=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 @@ -1332,7 +1344,7 @@ def show_form_popup(self, title, fields, passwd_fields=[], required=[], callback self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') - def show_filedialog_popup(self, popup_type='openfile', initial_dir='.', callback=None, ascii_icons=True, limit_extensions=[]): + 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 @@ -1364,7 +1376,7 @@ def show_filedialog_popup(self, popup_type='openfile', initial_dir='.', callback self._logger.debug(f'Opened {str(type(self._popup))} popup with type {popup_type}') - def increment_loading_bar(self): + def increment_loading_bar(self) -> None: """Increments progress bar if loading bar popup is open """ @@ -1374,7 +1386,7 @@ def increment_loading_bar(self): self._logger.warn('No popup is currently opened.') - def stop_loading_popup(self): + def stop_loading_popup(self) -> None: """Leaves loading state, and closes popup. Must be called by user to escape loading. @@ -1385,7 +1397,7 @@ def stop_loading_popup(self): self._logger.debug('Stopping open loading popup') - def close_popup(self): + def close_popup(self) -> None: """Closes the popup, and resets focus """ @@ -1393,7 +1405,7 @@ def close_popup(self): self._popup = None - def _refresh_height_width(self): + 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: @@ -1416,14 +1428,15 @@ def _refresh_height_width(self): self._width = width self._grid.update_grid_height_width(self._height, self._width) for widget_id in self.get_widgets().keys(): - if self.get_widgets()[widget_id] is not None: - self.get_widgets()[widget_id].update_height_width() + 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: self._popup.update_height_width() if self._logger._live_debug_element is not None: self._logger._live_debug_element.update_height_width() - def get_absolute_size(self): + def get_absolute_size(self) -> Tuple[int,int]: """Returns dimensions of CUI Returns @@ -1436,18 +1449,21 @@ def get_absolute_size(self): # Draw Functions. Function for drawing widgets, status bars, and popups - def _draw_widgets(self): + def _draw_widgets(self) -> None: """Function that draws all of the widgets to the screen """ for widget_id in self.get_widgets().keys(): if widget_id != self._selected_widget: - if self.get_widgets()[widget_id] is not None: - self.get_widgets()[widget_id]._draw() + widget = self.get_widgets()[widget_id] + if widget is not None: + widget._draw() # We draw the selected widget last to support cursor location. if self._selected_widget is not None: - self.get_widgets()[self._selected_widget]._draw() + widget = self.get_widgets()[self._selected_widget] + if widget is not None: + widget._draw() if self._logger is not None and self._logger.is_live_debug_enabled(): self._logger.draw_live_debug() @@ -1455,7 +1471,7 @@ def _draw_widgets(self): self._logger.info('Drew widgets') - def _draw_status_bars(self, stdscr, height, width): + def _draw_status_bars(self, stdscr, height: int, width: int) -> None: """Draws status bar and title bar Parameters @@ -1479,7 +1495,7 @@ def _draw_status_bars(self, stdscr, height, width): stdscr.attroff(curses.color_pair(self.title_bar.get_color())) - def _display_window_warning(self, stdscr, error_info): + 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 Parameters @@ -1500,7 +1516,7 @@ def _display_window_warning(self, stdscr, error_info): self._logger.error(f'Encountered error -> {error_info}') - def _handle_key_presses(self, key_pressed): + def _handle_key_presses(self, key_pressed: int) -> None: """Function that handles all main loop key presses. Parameters @@ -1514,7 +1530,8 @@ def _handle_key_presses(self, key_pressed): return selected_widget = self.get_widgets()[self._selected_widget] - + if selected_widget is None: + return # If logging is enabled, the Ctrl + D key code will enable "live-debug" # mode, where debug messages are printed on the screen if self._logger is not None and self._toggle_live_debug_key is not None: @@ -1555,7 +1572,9 @@ def _handle_key_presses(self, key_pressed): neighbor = self._check_if_neighbor_exists(key_pressed) if neighbor is not None: self.set_selected_widget(neighbor) - self._logger.debug(f'Navigated to neighbor widget {self.get_widgets()[self._selected_widget].get_title()}') + widget = self.get_widgets()[self._selected_widget] + if widget is not None: + 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: @@ -1563,11 +1582,11 @@ def _handle_key_presses(self, key_pressed): self._popup._handle_key_press(key_pressed) - def _draw(self, stdscr): + def _draw(self, stdscr) -> None: """Main CUI draw loop called by start() Parameters - ---------- + -------- stdscr : curses Standard screen The screen buffer used for drawing CUI elements """ @@ -1591,7 +1610,7 @@ def _draw(self, stdscr): self._stdscr.timeout(self._refresh_timeout) # If user sets non-default border characters, update them here - if self._border_characters is not 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 From fe9f5091fde69fa47655dd05b8d05b5b90155877 Mon Sep 17 00:00:00 2001 From: mohan-cloud Date: Sun, 20 Jun 2021 06:43:39 +0000 Subject: [PATCH 26/40] add annotations - WIP --- py_cui/colors.py | 21 +++--- py_cui/renderer.py | 66 +++++++++-------- py_cui/statusbar.py | 20 +++--- py_cui/ui.py | 172 ++++++++++++++++++++++---------------------- 4 files changed, 144 insertions(+), 135 deletions(-) diff --git a/py_cui/colors.py b/py_cui/colors.py index 02e09ce..bb1cf07 100644 --- a/py_cui/colors.py +++ b/py_cui/colors.py @@ -5,6 +5,7 @@ # Created: 12-Aug-2019 +from typing import List, Tuple, Union import py_cui import curses import re @@ -167,7 +168,7 @@ class ColorRule: Flag to determine whether to strip whitespace before matching. """ - def __init__(self, regex, color, selected_color, rule_type, match_type, region, include_whitespace, logger): + def __init__(self, regex: str, color: int, selected_color: int, rule_type: str, match_type: str, region: List[int], include_whitespace: bool, logger): """Constructor for ColorRule object Parameters @@ -205,7 +206,7 @@ def __init__(self, regex, color, selected_color, rule_type, match_type, region, self._logger = logger - def _check_match(self, line): + def _check_match(self, line: str) -> bool: """Checks if the color rule matches a line Parameters @@ -242,7 +243,7 @@ def _check_match(self, line): return False - def _generate_fragments_regex(self, widget, render_text, selected): + def _generate_fragments_regex(self, widget: Union['py_cui.widgets.Widget','py_cui.ui.UIElement'], render_text:str, selected) -> List[List[Union[int,str]]]: """Splits text into fragments based on regular expression Parameters @@ -258,7 +259,7 @@ def _generate_fragments_regex(self, widget, render_text, selected): the render text split into fragments of strings paired with colors """ - fragments = [] + fragments: List[List[Union[int,str]]] = [] matches = re.findall(self._regex, render_text) current_render_text = render_text for match in matches: @@ -280,9 +281,9 @@ def _generate_fragments_regex(self, widget, render_text, selected): return fragments - def _split_text_on_region(self, widget, render_text, selected): + def _split_text_on_region(self, widget: Union['py_cui.widgets.Widget','py_cui.ui.UIElement'], render_text: str, selected) -> List[List[Union[str,int]]]: # renderer._generate_text_color_fragments passes a uielement and not a widget """Splits text into fragments based on region - + Parameters ---------- widget : py_cui.Widget @@ -296,7 +297,7 @@ def _split_text_on_region(self, widget, render_text, selected): the render text split into fragments of strings paired with colors """ - fragments = [] + fragments: List[List[Union[int,str]]] = [] if self._region is None or len(render_text) < self._region[0]: if selected: @@ -322,7 +323,7 @@ def _split_text_on_region(self, widget, render_text, selected): return fragments - def generate_fragments(self, widget, line, render_text, selected=False): + def generate_fragments(self, widget: Union['py_cui.widgets.Widget','py_cui.ui.UIElement'], line: str, render_text: str, selected=False) -> Tuple[List[List[Union[str,int]]],bool]: """Splits text into fragments if matched line to regex Parameters @@ -341,10 +342,10 @@ def generate_fragments(self, widget, line, render_text, selected=False): matched : bool Boolean output saying if a match was found in the line. """ - + fragments: List[List[Union[int,str]]] = [] match = self._check_match(line) if selected: - fragments = [[render_text, widget.get_selected_color()]] + fragments = [[render_text, widget.get_selected_color()]] # tuple might be better? else: fragments = [[render_text, widget.get_color()]] diff --git a/py_cui/renderer.py b/py_cui/renderer.py index 19fc088..6b86d7f 100644 --- a/py_cui/renderer.py +++ b/py_cui/renderer.py @@ -7,6 +7,10 @@ import curses import py_cui import py_cui.colors +from typing import Any, Dict, Tuple, List, TYPE_CHECKING, Union, cast + +if TYPE_CHECKING: + import py_cui.ui class Renderer: @@ -25,13 +29,13 @@ class Renderer: List of currently loaded rules to apply during drawing """ - def __init__(self, root, stdscr, logger): + def __init__(self, root: 'py_cui.PyCUI', stdscr, logger): """Constructor for renderer object """ self._root = root self._stdscr = stdscr - self._color_rules = [] + self._color_rules: List['py_cui.colors.ColorRule'] = [] self._logger = logger # Define ui_element border characters @@ -45,7 +49,7 @@ def __init__(self, root, stdscr, logger): } - def _set_border_renderer_chars(self, border_char_set): + def _set_border_renderer_chars(self, border_char_set: Dict[str,str]) -> None: """Function that sets the border characters for ui_elements Parameters @@ -62,21 +66,21 @@ def _set_border_renderer_chars(self, border_char_set): self._border_characters['VERTICAL' ] = border_char_set['VERTICAL' ] - def _set_bold(self): + def _set_bold(self) -> None: """Sets bold draw mode """ self._stdscr.attron(curses.A_BOLD) - def _unset_bold(self): + def _unset_bold(self) -> None: """Unsets bold draw mode """ self._stdscr.attroff(curses.A_BOLD) - def set_color_rules(self, color_rules): + def set_color_rules(self, color_rules) -> None: """Sets current color rules Parameters @@ -88,7 +92,7 @@ def set_color_rules(self, color_rules): self._color_rules = color_rules - def set_color_mode(self, color_mode): + def set_color_mode(self, color_mode: int) -> None: """Sets the output color mode Parameters @@ -100,7 +104,7 @@ def set_color_mode(self, color_mode): self._stdscr.attron(curses.color_pair(color_mode)) - def unset_color_mode(self, color_mode): + def unset_color_mode(self, color_mode: int) -> None: """Unsets the output color mode Parameters @@ -112,7 +116,7 @@ def unset_color_mode(self, color_mode): self._stdscr.attroff(curses.color_pair(color_mode)) - def reset_cursor(self, ui_element, fill=True): + def reset_cursor(self, ui_element: 'py_cui.ui.UIElement', fill: bool=True) -> None: """Positions the cursor at the bottom right of the selected element Parameters @@ -139,7 +143,7 @@ def reset_cursor(self, ui_element, fill=True): self._stdscr.move(0,0) - def draw_cursor(self, cursor_y, cursor_x): + def draw_cursor(self, cursor_y: int, cursor_x: int) -> None: """Draws the cursor at a particular location Parameters @@ -151,7 +155,7 @@ def draw_cursor(self, cursor_y, cursor_x): self._stdscr.move(cursor_y, cursor_x) - def draw_border(self, ui_element, fill=True, with_title=True): + def draw_border(self, ui_element: 'py_cui.ui.UIElement', fill: bool=True, with_title: bool=True) -> None: """Draws ascii border around ui element Parameters @@ -193,7 +197,7 @@ def draw_border(self, ui_element, fill=True, with_title=True): self._unset_bold() - def _draw_border_top(self, ui_element, y, with_title): + def _draw_border_top(self, ui_element:'py_cui.ui.UIElement', y: int, with_title: bool) -> None: """Internal function for drawing top of border Parameters @@ -225,7 +229,7 @@ def _draw_border_top(self, ui_element, y, with_title): self._stdscr.addstr(y, start_x + padx, render_text) - def _draw_border_bottom(self, ui_element, y): + def _draw_border_bottom(self, ui_element: 'py_cui.ui.UIElement', y: int) -> None: """Internal function for drawing bottom of border Parameters @@ -246,7 +250,7 @@ def _draw_border_bottom(self, ui_element, y): self._stdscr.addstr(y, start_x + padx, render_text) - def _draw_blank_row(self, ui_element, y): + def _draw_blank_row(self, ui_element: 'py_cui.ui.UIElement', y: int) -> None: """Internal function for drawing a blank row Parameters @@ -268,7 +272,7 @@ def _draw_blank_row(self, ui_element, y): self._stdscr.addstr(y, start_x + padx, render_text) - def _get_render_text(self, ui_element, line, centered, bordered, selected, start_pos): + def _get_render_text(self, ui_element: 'py_cui.ui.UIElement', line: str, centered: bool, bordered: bool, selected, start_pos:int) -> List[List[Union[int,str]]]: """Internal function that computes the scope of the text that should be drawn Parameters @@ -286,7 +290,7 @@ def _get_render_text(self, ui_element, line, centered, bordered, selected, start Returns ------- - render_text : str + render_text : str # to be checked, returns a List of [int,str] The text shortened to fit within given space """ @@ -306,11 +310,11 @@ def _get_render_text(self, ui_element, line, centered, bordered, selected, start else: render_text = line[start_pos:start_pos + render_text_length] - render_text_fragments = self._generate_text_color_fragments(ui_element, line, render_text, selected) + render_text_fragments = self._generate_text_color_fragments(ui_element, line, render_text, selected) # returns List[List[int,str]] return render_text_fragments - def _generate_text_color_fragments(self, ui_element, line, render_text, selected): + def _generate_text_color_fragments(self, ui_element: 'py_cui.ui.UIElement', line: str, render_text: str, selected) -> List[List[Union[int,str]]]: """Function that applies color rules to text, dividing them if match is found Parameters @@ -327,6 +331,7 @@ def _generate_text_color_fragments(self, ui_element, line, render_text, selected fragments : list of [int, str] list of text - color code combinations to write """ + fragments: List[List[Union[int,str]]] if selected: fragments = [[render_text, ui_element.get_selected_color()]] @@ -341,7 +346,7 @@ def _generate_text_color_fragments(self, ui_element, line, render_text, selected return fragments - def draw_text(self, ui_element, line, y, centered = False, bordered = True, selected = False, start_pos = 0): + def draw_text(self, ui_element: 'py_cui.ui.UIElement', line: str, y: int, centered: bool = False, bordered: bool = True, selected: bool = False, start_pos: int = 0): """Function that draws ui_element text. Parameters @@ -384,20 +389,21 @@ def draw_text(self, ui_element, line, y, centered = False, bordered = True, sele # Each text elem is a list with [text, color] for text_elem in render_text: - text_elem[0] = text_elem[0].replace(chr(0), "") - self.set_color_mode(text_elem[1]) + if isinstance(text_elem[0],str) and isinstance(text_elem[1],int): + text_elem[0] = text_elem[0].replace(chr(0), "") + self.set_color_mode(text_elem[1]) - # BLACK_ON_WHITE + BOLD is unreadable on windows terminals - if selected and text_elem[1] != py_cui.BLACK_ON_WHITE: - self._set_bold() + # BLACK_ON_WHITE + BOLD is unreadable on windows terminals + if selected and text_elem[1] != py_cui.BLACK_ON_WHITE: + self._set_bold() - self._stdscr.addstr(y, current_start_x, text_elem[0]) - current_start_x = current_start_x + len(text_elem[0]) + self._stdscr.addstr(y, current_start_x, text_elem[0]) + current_start_x = current_start_x + len(text_elem[0]) - if selected and text_elem[1] != py_cui.BLACK_ON_WHITE: - self._unset_bold() - - self.unset_color_mode(text_elem[1]) + if selected and text_elem[1] != py_cui.BLACK_ON_WHITE: + self._unset_bold() + + self.unset_color_mode(text_elem[1]) if ui_element.is_selected(): self._set_bold() diff --git a/py_cui/statusbar.py b/py_cui/statusbar.py index 0ebdd8b..8fc043a 100644 --- a/py_cui/statusbar.py +++ b/py_cui/statusbar.py @@ -5,7 +5,7 @@ # Author: Jakub Wlodek # Created: 12-Aug-2019 - +import py_cui class StatusBar: """Very simple class representing a status bar @@ -22,7 +22,7 @@ class StatusBar: Is the StatusBar displayed on the top of the grid """ - def __init__(self, text, color, root, is_title_bar=False): + def __init__(self, text: str, color: int, root: 'py_cui.PyCUI', is_title_bar=False): """Initializer for statusbar """ @@ -33,7 +33,7 @@ def __init__(self, text, color, root, is_title_bar=False): self.__is_title_bar = is_title_bar - def get_color(self): + def get_color(self) -> int: """Getter for status bar color Returns @@ -45,7 +45,7 @@ def get_color(self): return self.__color - def get_text(self): + def get_text(self) -> str: """Getter for status bar text Returns @@ -57,7 +57,7 @@ def get_text(self): return self.__text - def set_color(self, color): + def set_color(self, color) -> None: """Setter for statusbar color Parameters @@ -69,7 +69,7 @@ def set_color(self, color): self.__color = color - def set_text(self, text): + def set_text(self, text: str) -> None : """Sets the statusbar text Parameters @@ -80,7 +80,7 @@ def set_text(self, text): self.__text = text - def get_height(self): + def get_height(self) -> int : """Getter for status bar height in row Returns @@ -91,19 +91,19 @@ def get_height(self): return self.__height - def show(self): + def show(self) -> None: """Sets the status bar height to 1""" self.__height = 1 self._refresh_root_size() - def hide(self): + def hide(self) -> None: """Sets the status bar height to 0""" self.__height = 0 self._refresh_root_size() - def _refresh_root_size(self): + def _refresh_root_size(self) -> None: """Resets the grid's title bar offset if needed and calls a UI size update.""" if self.__is_title_bar: diff --git a/py_cui/ui.py b/py_cui/ui.py index 7b91c08..aa4fbb7 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -7,6 +7,7 @@ # Created: 19-Mar-2020 +from typing import Any, List, NoReturn, Optional, Tuple, Union import py_cui import py_cui.errors import py_cui.colors @@ -73,21 +74,21 @@ def __init__(self, id, title, renderer, logger): self._help_text = '' - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Must be implemented by subclass, computes the absolute coords of upper-left corner """ raise NotImplementedError - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Must be implemented by subclass, computes the absolute coords of bottom-right corner """ raise NotImplementedError - def get_absolute_dimensions(self): + def get_absolute_dimensions(self) -> Tuple[int,int]: """Gets dimensions of element in terminal characters Returns @@ -100,7 +101,7 @@ def get_absolute_dimensions(self): return (stop_y - start_y), (stop_x - start_x) - def update_height_width(self): + def update_height_width(self) -> None: """Function that refreshes position and dimensons on resize. If necessary, make sure required widget attributes updated here as well. @@ -111,7 +112,7 @@ def update_height_width(self): self._height, self._width = self.get_absolute_dimensions() - def get_viewport_height(self): + def get_viewport_height(self) -> int: """Gets the height of the element viewport (height minus padding and borders) Returns @@ -123,7 +124,7 @@ def get_viewport_height(self): return self._height - (2 * self._pady) - 3 - def get_id(self): + def get_id(self) -> int: """Gets the element ID Returns @@ -135,7 +136,7 @@ def get_id(self): return self._id - def get_title(self): + def get_title(self) -> str: """Getter for ui element title Returns @@ -147,7 +148,7 @@ def get_title(self): return self._title - def get_padding(self): + def get_padding(self) -> Tuple[int,int]: """Gets ui element padding on in characters Returns @@ -159,7 +160,7 @@ def get_padding(self): return self._padx, self._pady - def get_start_position(self): + def get_start_position(self) -> Tuple[int,int]: """Gets coords of upper left corner Returns @@ -171,7 +172,7 @@ def get_start_position(self): return self._start_x, self._start_y - def get_stop_position(self): + def get_stop_position(self) -> Tuple[int,int]: """Gets coords of lower right corner Returns @@ -183,7 +184,7 @@ def get_stop_position(self): return self._stop_x, self._stop_y - def get_color(self): + def get_color(self) -> int: """Gets current element color Returns @@ -195,7 +196,7 @@ def get_color(self): return self._color - def get_border_color(self): + def get_border_color(self) -> int: """Gets current element border color Returns @@ -210,7 +211,7 @@ def get_border_color(self): return self._border_color - def get_selected_color(self): + def get_selected_color(self) -> int: """Gets current selected item color Returns @@ -222,7 +223,7 @@ def get_selected_color(self): return self._selected_color - def is_selected(self): + def is_selected(self) -> bool: """Get selected status Returns @@ -234,7 +235,7 @@ def is_selected(self): return self._selected - def get_renderer(self): + def get_renderer(self) -> 'py_cui.renderer.Renderer': """Gets reference to renderer object Returns @@ -246,7 +247,7 @@ def get_renderer(self): return self._renderer - def get_help_text(self): + def get_help_text(self) -> str: """Returns current help text Returns @@ -258,7 +259,7 @@ def get_help_text(self): return self._help_text - def set_title(self, title): + def set_title(self, title: str): """Function that sets the widget title. Parameters @@ -270,7 +271,7 @@ def set_title(self, title): self._title = title - def set_color(self, color): + def set_color(self, color: int) -> None: """Sets element default color Parameters @@ -288,7 +289,7 @@ def set_color(self, color): self._color = color - def set_border_color(self, color): + def set_border_color(self, color: int) -> None: """Sets element border color Parameters @@ -300,7 +301,7 @@ def set_border_color(self, color): self._border_color = color - def set_focus_border_color(self, color): + def set_focus_border_color(self, color: int) -> None: """Sets element border color if the current element is focused @@ -313,7 +314,7 @@ def set_focus_border_color(self, color): self._focus_border_color = color - def set_selected_color(self, color): + def set_selected_color(self, color: int) -> None: """Sets element sected color Parameters @@ -325,7 +326,7 @@ def set_selected_color(self, color): self._selected_color = color - def set_selected(self, selected): + def set_selected(self, selected: bool) -> None: """Marks the UI element as selected or not selected Parameters @@ -337,7 +338,7 @@ def set_selected(self, selected): self._selected = selected - def set_help_text(self, help_text): + def set_help_text(self, help_text: str) -> None: """Sets status bar help text Parameters @@ -349,7 +350,7 @@ def set_help_text(self, help_text): self._help_text = help_text - def set_focus_text(self, focus_text): + def set_focus_text(self, focus_text: str) -> None: """Sets status bar focus text. Legacy function, overridden by set_focus_text Parameters @@ -387,7 +388,7 @@ def _draw(self): raise NotImplementedError - def _assign_renderer(self, renderer, quiet=False): + def _assign_renderer(self, renderer: 'py_cui.renderer.Renderer', quiet: bool=False) : """Function that assigns a renderer object to the element (Meant for internal usage only) @@ -413,7 +414,7 @@ def _assign_renderer(self, renderer, quiet=False): raise py_cui.errors.PyCUIError('Invalid renderer, must be of type py_cui.renderer.Renderer') - def _contains_position(self, x, y): + def _contains_position(self, x: int, y: int) -> bool: """Checks if character position is within element. Parameters @@ -472,7 +473,7 @@ class TextBoxImplementation(UIImplementation): Toggle to display password characters or text """ - def __init__(self, initial_text, password, logger): + def __init__(self, initial_text: str, password: bool , logger): """Initializer for the TextBoxImplementation base class """ @@ -489,7 +490,7 @@ def __init__(self, initial_text, password, logger): # Variable getter + setter functions - def get_initial_cursor_pos(self): + def get_initial_cursor_pos(self) -> int: """Gets initial cursor position Returns @@ -501,7 +502,7 @@ def get_initial_cursor_pos(self): return self._initial_cursor - def get_cursor_text_pos(self): + def get_cursor_text_pos(self) -> int: """Gets current position of cursor relative to text Returns @@ -513,7 +514,7 @@ def get_cursor_text_pos(self): return self._cursor_text_pos - def get_cursor_limits(self): + def get_cursor_limits(self) -> Tuple[int,int]: """Gets cursor extreme points in terminal position Returns @@ -525,7 +526,7 @@ def get_cursor_limits(self): return self._cursor_max_left, self._cursor_max_right - def get_cursor_position(self): + def get_cursor_position(self) -> Tuple[int,int]: """Returns current cursor poition Returns @@ -537,7 +538,7 @@ def get_cursor_position(self): return self._cursor_x, self._cursor_y - def get_viewport_width(self): + def get_viewport_width(self) -> int: """Gets the width of the textbox viewport Returns @@ -549,7 +550,7 @@ def get_viewport_width(self): return self._viewport_width - def set_text(self, text): + def set_text(self, text: str): """Sets the value of the text. Overwrites existing text Parameters @@ -565,7 +566,7 @@ def set_text(self, text): self._cursor_x = self._cursor_x - diff - def get(self): + def get(self) -> str: """Gets value of the text in the textbox Returns @@ -577,7 +578,7 @@ def get(self): return self._text - def clear(self): + def clear(self) -> None: """Clears the text in the textbox """ @@ -586,7 +587,7 @@ def clear(self): self._text = '' - def _move_left(self): + def _move_left(self) -> None: """Shifts the cursor the the left. Internal use only """ @@ -596,7 +597,7 @@ def _move_left(self): self._cursor_text_pos = self._cursor_text_pos - 1 - def _move_right(self): + def _move_right(self) -> None: """Shifts the cursor the the right. Internal use only """ if self._cursor_text_pos < len(self._text): @@ -605,7 +606,7 @@ def _move_right(self): self._cursor_text_pos = self._cursor_text_pos + 1 - def _insert_char(self, key_pressed): + def _insert_char(self, key_pressed: int) -> None: """Inserts char at cursor position. Internal use only Parameters @@ -619,7 +620,7 @@ def _insert_char(self, key_pressed): self._cursor_text_pos = self._cursor_text_pos + 1 - def _jump_to_start(self): + def _jump_to_start(self) -> None: """Jumps to the start of the textbox. Internal use only """ @@ -627,7 +628,7 @@ def _jump_to_start(self): self._cursor_text_pos = 0 - def _jump_to_end(self): + def _jump_to_end(self) -> None: """Jumps to the end to the textbox. Internal use only """ @@ -635,7 +636,7 @@ def _jump_to_end(self): self._cursor_x = self._initial_cursor + self._cursor_text_pos - def _erase_char(self): + def _erase_char(self) -> None: """Erases character at textbox cursor. Internal Use only """ @@ -646,7 +647,7 @@ def _erase_char(self): self._cursor_text_pos = self._cursor_text_pos - 1 - def _delete_char(self): + def _delete_char(self) -> None: """Deletes character to right of texbox cursor. Internal use only """ @@ -693,7 +694,7 @@ def clear(self): - def get_selected_item_index(self): + def get_selected_item_index(self) -> int: """Gets the currently selected item Returns @@ -705,7 +706,7 @@ def get_selected_item_index(self): return self._selected_item - def set_selected_item_index(self, selected_item_index): + def set_selected_item_index(self, selected_item_index: int) -> None: """Sets the currently selected item Parameters @@ -717,7 +718,7 @@ def set_selected_item_index(self, selected_item_index): self._selected_item = selected_item_index - def _scroll_up(self): + def _scroll_up(self) -> None: """Function that scrolls the view up in the scroll menu """ @@ -729,7 +730,7 @@ def _scroll_up(self): self._logger.debug(f'Scrolling up to item {self._selected_item}') - def _scroll_down(self, viewport_height): + def _scroll_down(self, viewport_height: int) -> None: """Function that scrolls the view down in the scroll menu TODO: Viewport height should be calculated internally, and not rely on a parameter. @@ -748,7 +749,7 @@ def _scroll_down(self, viewport_height): self._logger.debug(f'Scrolling down to item {self._selected_item}') - def _jump_up(self): + def _jump_up(self) -> None: """Function for jumping up menu several spots at a time """ @@ -756,7 +757,7 @@ def _jump_up(self): self._scroll_up() - def _jump_down(self, viewport_height): + def _jump_down(self, viewport_height: int) -> None: """Function for jumping down the menu several spots at a time Parameters @@ -769,7 +770,7 @@ def _jump_down(self, viewport_height): self._scroll_down(viewport_height) - def _jump_to_top(self): + def _jump_to_top(self) -> None: """Function that jumps to the top of the menu """ @@ -777,7 +778,7 @@ def _jump_to_top(self): self._selected_item = 0 - def _jump_to_bottom(self, viewport_height): + def _jump_to_bottom(self, viewport_height: int) -> None: """Function that jumps to the bottom of the menu Parameters @@ -792,7 +793,7 @@ def _jump_to_bottom(self, viewport_height): self._top_view = 0 - def add_item(self, item): + def add_item(self, item: Any): # How to type hint item? - typing.Portocol supports from python3.7 only """Adds an item to the menu. Parameters @@ -805,7 +806,8 @@ def add_item(self, item): self._view_items.append(item) - def add_item_list(self, item_list): + def add_item_list(self, item_list: List[Any]): # how to type hint item? - typing.Portocol supports from python3.7 only + """Adds a list of items to the scroll menu. Parameters @@ -819,7 +821,7 @@ def add_item_list(self, item_list): self.add_item(item) - def remove_selected_item(self): + def remove_selected_item(self) -> None: """Function that removes the selected item from the scroll menu. """ @@ -831,7 +833,7 @@ def remove_selected_item(self): self._selected_item = self._selected_item - 1 - def remove_item(self, item): + def remove_item(self, item) -> None: """Function that removes a specific item from the menu Parameters @@ -849,7 +851,7 @@ def remove_item(self, item): self._selected_item = self._selected_item - 1 - def get_item_list(self): + def get_item_list(self) -> List[Any]: """Function that gets list of items in a scroll menu Returns @@ -861,7 +863,7 @@ def get_item_list(self): return self._view_items - def get(self): + def get(self) -> Optional[Any]: """Function that gets the selected item from the scroll menu Returns @@ -875,7 +877,7 @@ def get(self): return None - def set_selected_item(self, selected_item): + def set_selected_item(self, selected_item: Any): """Function that replaces the currently selected item with a new item Parameters @@ -921,7 +923,7 @@ def add_item(self, item): self._selected_item_dict[item] = False - def remove_selected_item(self): + def remove_selected_item(self) -> None: """Removes selected item from item list and selected item dictionary """ @@ -929,7 +931,7 @@ def remove_selected_item(self): super().remove_selected_item() - def remove_item(self, item): + def remove_item(self, item) -> None: """Removes item from item list and selected item dict Parameters @@ -942,7 +944,7 @@ def remove_item(self, item): super().remove_item(item) - def toggle_item_checked(self, item): + def toggle_item_checked(self, item: Any): """Function that marks an item as selected Parameters @@ -954,7 +956,7 @@ def toggle_item_checked(self, item): self._selected_item_dict[item] = not self._selected_item_dict[item] - def mark_item_as_checked(self, item): + def mark_item_as_checked(self, item: Any) -> None: """Function that marks an item as selected Parameters @@ -966,7 +968,7 @@ def mark_item_as_checked(self, item): self._selected_item_dict[item] = True - def mark_item_as_not_checked(self, item): + def mark_item_as_not_checked(self, item) -> None: """Function that marks an item as selected Parameters @@ -1002,7 +1004,7 @@ class TextBlockImplementation(UIImplementation): The dimensions of the viewport in characters """ - def __init__(self, initial_text, logger): + def __init__(self, initial_text: str, logger): """Initializer for TextBlockImplementation base class Zeros attributes, and parses initial text @@ -1029,7 +1031,7 @@ def __init__(self, initial_text, logger): # Getters and setters - def get_viewport_start_pos(self): + def get_viewport_start_pos(self) -> Tuple[int,int]: """Gets upper left corner position of viewport Returns @@ -1041,7 +1043,7 @@ def get_viewport_start_pos(self): return self._viewport_x_start, self._viewport_y_start - def get_viewport_dims(self): + def get_viewport_dims(self) -> Tuple[int,int]: """Gets viewport dimensions in characters Returns @@ -1053,7 +1055,7 @@ def get_viewport_dims(self): return self._viewport_height, self._viewport_width - def get_cursor_text_pos(self): + def get_cursor_text_pos(self) -> Tuple[int,int]: """Gets cursor postion relative to text Returns @@ -1066,7 +1068,7 @@ def get_cursor_text_pos(self): return self._cursor_text_pos_x, self._cursor_text_pos_y - def get_abs_cursor_position(self): + def get_abs_cursor_position(self) -> Tuple[int,int]: """Gets absolute cursor position in terminal characters Returns @@ -1078,7 +1080,7 @@ def get_abs_cursor_position(self): return self._cursor_x, self._cursor_y - def get_cursor_limits_vertical(self): + def get_cursor_limits_vertical(self) -> Tuple[int,int]: """Gets limits for cursor in vertical direction Returns @@ -1090,7 +1092,7 @@ def get_cursor_limits_vertical(self): return self._cursor_max_up, self._cursor_max_down - def get_cursor_limits_horizontal(self): + def get_cursor_limits_horizontal(self) -> Tuple[int,int]: """Gets limits for cursor in horizontal direction Returns @@ -1102,7 +1104,7 @@ def get_cursor_limits_horizontal(self): return self._cursor_max_left, self._cursor_max_right - def get(self): + def get(self) -> str: """Gets all of the text in the textblock and returns it Returns @@ -1117,7 +1119,7 @@ def get(self): return text - def write(self, text): + def write(self, text: str) -> None: """Function used for writing text to the text block Parameters @@ -1133,7 +1135,7 @@ def write(self, text): self._text_lines.extend(lines) - def clear(self): + def clear(self) -> None: """Function that clears the text block """ @@ -1146,7 +1148,7 @@ def clear(self): self._logger.info('Cleared textblock') - def get_current_line(self): + def get_current_line(self) -> str: """Returns the line on which the cursor currently resides Returns @@ -1158,7 +1160,7 @@ def get_current_line(self): return self._text_lines[self._cursor_text_pos_y] - def set_text(self, text): + def set_text(self, text: str) -> None: """Function that sets the text for the textblock. Note that this will overwrite any existing text @@ -1180,7 +1182,7 @@ def set_text(self, text): self._cursor_text_pos_x = 0 - def set_text_line(self, text): + def set_text_line(self, text: str) -> None: """Function that sets the current line's text. Meant only for internal use @@ -1194,7 +1196,7 @@ def set_text_line(self, text): self._text_lines[self._cursor_text_pos_y] = text - def _move_left(self): + def _move_left(self) -> None: """Function that moves the cursor/text position one location to the left """ @@ -1208,7 +1210,7 @@ def _move_left(self): self._logger.debug(f'Moved cursor left to pos {self._cursor_text_pos_x}') - def _move_right(self): + def _move_right(self) -> None: """Function that moves the cursor/text position one location to the right """ @@ -1224,7 +1226,7 @@ def _move_right(self): self._logger.debug(f'Moved cursor right to pos {self._cursor_text_pos_x}') - def _move_up(self): + def _move_up(self) -> None: """Function that moves the cursor/text position one location up """ @@ -1243,7 +1245,7 @@ def _move_up(self): self._logger.debug(f'Moved cursor up to line {self._cursor_text_pos_y}') - def _move_down(self): + def _move_down(self) -> None: """Function that moves the cursor/text position one location down """ @@ -1262,7 +1264,7 @@ def _move_down(self): - def _handle_newline(self): + def _handle_newline(self) -> None: """Function that handles recieving newline characters in the text """ @@ -1283,7 +1285,7 @@ def _handle_newline(self): self._viewport_y_start = self._viewport_y_start + 1 - def _handle_backspace(self): + def _handle_backspace(self) -> None: """Function that handles recieving backspace characters in the text """ @@ -1307,7 +1309,7 @@ def _handle_backspace(self): self._cursor_text_pos_x = self._cursor_text_pos_x - 1 - def _handle_home(self): + def _handle_home(self) -> None: """Function that handles recieving a home keypress """ @@ -1318,7 +1320,7 @@ def _handle_home(self): self._viewport_x_start = 0 - def _handle_end(self): + def _handle_end(self) -> None: """Function that handles recieving an end keypress """ @@ -1333,7 +1335,7 @@ def _handle_end(self): self._cursor_x = self._cursor_max_left + len(current_line) - def _handle_delete(self): + def _handle_delete(self) -> None: """Function that handles recieving a delete keypress """ @@ -1347,7 +1349,7 @@ def _handle_delete(self): self.set_text_line(current_line[:self._cursor_text_pos_x] + current_line[self._cursor_text_pos_x+1:]) - def _insert_char(self, key_pressed): + def _insert_char(self, key_pressed: int) -> None: """Function that handles recieving a character Parameters From 3067b0225ed89a77f9e42383032751c75df042f7 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sun, 20 Jun 2021 11:27:24 -0400 Subject: [PATCH 27/40] Fixing #132 - allow for objects in menu popups --- py_cui/popups.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py_cui/popups.py b/py_cui/popups.py index 097faaf..1b26117 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -416,7 +416,8 @@ def _draw(self): self._renderer.set_color_rules([]) counter = self._pady + 1 line_counter = 0 - for line in self._view_items: + for item in self._view_items: + line = str(item) if line_counter < self._top_view: line_counter = line_counter + 1 else: From b84bdbbf4f4cab895d75e248514a9a5a3b99f43c Mon Sep 17 00:00:00 2001 From: mohan-cloud Date: Sun, 20 Jun 2021 17:56:38 +0000 Subject: [PATCH 28/40] add types form,dialog,widgets --- py_cui/__init__.py | 30 +++++++---- py_cui/controls/slider.py | 25 +++++----- py_cui/debug.py | 50 ++++++++++--------- py_cui/dialogs/filedialog.py | 97 +++++++++++++++++++----------------- py_cui/dialogs/form.py | 58 ++++++++++----------- py_cui/grid.py | 17 ++++--- py_cui/keys.py | 5 +- py_cui/popups.py | 45 +++++++++-------- py_cui/widget_set.py | 41 ++++++++------- py_cui/widgets.py | 2 +- 10 files changed, 198 insertions(+), 172 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 091e498..9e9c17a 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -43,7 +43,7 @@ __version__ = '0.1.4' -def fit_text(width: int, text: str, center: bool=False) -> str: +def fit_text(width: int, text: str, center: bool = False) -> str: """Fits text to screen size Helper function to fit text within a given width. Used to fix issue with status/title bar text @@ -128,8 +128,8 @@ def __init__(self, num_rows: int, num_cols: int, auto_focus_buttons: bool=True, height = term_size.lines width = term_size.columns else: - height = self._simulated_terminal[0] - width = self._simulated_terminal[1] + height = self._simulated_terminal[0] + 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) @@ -488,7 +488,8 @@ def add_scroll_menu(self, title: str, row: int, column: int, row_span: int=1, co padx, pady, self._logger) - new_scroll_menu._assign_renderer(self._renderer) + 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) @@ -536,7 +537,8 @@ def add_checkbox_menu(self, title: str, row: int, column: int, row_span: int=1, pady, self._logger, checked_char) - new_checkbox_menu._assign_renderer(self._renderer) + 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) @@ -585,7 +587,8 @@ def add_text_box(self, title: str, row: int, column: int, row_span: int = 1, col self._logger, initial_text, password) - new_text_box._assign_renderer(self._renderer) + 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) @@ -633,7 +636,8 @@ def add_text_block(self, title: str, row: int, column: int, row_span: int = 1, c pady, self._logger, initial_text) - new_text_block._assign_renderer(self._renderer) + 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) @@ -678,7 +682,8 @@ def add_label(self, title: str, row: int, column: int, row_span: int = 1, column padx, pady, self._logger) - new_label._assign_renderer(self._renderer) + 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 @@ -724,7 +729,8 @@ def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, pady, center, self._logger) - new_label._assign_renderer(self._renderer) + 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 @@ -770,7 +776,8 @@ def add_button(self, title: str, row: int, column: int, row_span: int = 1, colum pady, self._logger, command) - new_button._assign_renderer(self._renderer) + 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) @@ -829,7 +836,8 @@ def add_slider(self, title: str, row: int, column: int, row_span: int=1, max_val, step, init_val) - new_slider._assign_renderer(self._renderer) + 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 diff --git a/py_cui/controls/slider.py b/py_cui/controls/slider.py index c48e9f8..4f618aa 100644 --- a/py_cui/controls/slider.py +++ b/py_cui/controls/slider.py @@ -1,6 +1,7 @@ import py_cui.ui import py_cui.widgets import py_cui.popups +import py_cui.errors class SliderImplementation(py_cui.ui.UIImplementation): @@ -20,7 +21,7 @@ def __init__(self, min_val, max_val, init_val, step, logger): f'initial value must be between {self._min_val} and {self._max_val}') - def set_bar_char(self, char): + def set_bar_char(self, char: str): """ Updates the character used to represent the slider bar. @@ -61,7 +62,7 @@ def update_slider_value(self, offset: int) -> float: return self._cur_val - def get_slider_value(self): + def get_slider_value(self) -> float: """ Returns current slider value. @@ -74,7 +75,7 @@ def get_slider_value(self): return self._cur_val - def set_slider_step(self, step): + def set_slider_step(self, step: int) -> None: """ Changes the step value. @@ -119,46 +120,46 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, self.set_help_text("Focus mode on Slider. Use left/right to adjust value. Esc to exit.") - def toggle_title(self): + def toggle_title(self) -> None: """Toggles visibility of the widget's name. """ self._title_enabled = not self._title_enabled - def toggle_border(self): + def toggle_border(self) -> None: """Toggles visibility of the widget's border. """ self._border_enabled = not self._border_enabled - def toggle_value(self): + def toggle_value(self) -> None: """Toggles visibility of the widget's current value in integer. """ self._display_value = not self._display_value - def align_to_top(self): + def align_to_top(self) -> None: """Aligns widget height to top. """ self._alignment = "top" - def align_to_middle(self): + def align_to_middle(self) -> None: """Aligns widget height to middle. default configuration. """ self._alignment = "mid" - def align_to_bottom(self): + def align_to_bottom(self) -> None: """Aligns widget height to bottom. """ self._alignment = "btm" - def _custom_draw_with_border(self, start_y: int, content: str): + def _custom_draw_with_border(self, start_y: int, content: str) -> None: """ Custom method made from renderer.draw_border to support alignment for bordered variants. @@ -221,7 +222,7 @@ def _generate_bar(self, width: int) -> str: return progress - def _draw(self): + def _draw(self) -> None: """Override of base class draw function. """ @@ -256,7 +257,7 @@ def _draw(self): self._renderer.unset_color_mode(self._color) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """ LEFT_ARROW decreases value, RIGHT_ARROW increases. diff --git a/py_cui/debug.py b/py_cui/debug.py index 64c0349..532a33a 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -7,11 +7,13 @@ import os import logging import inspect +from types import FrameType +from typing import Any, NoReturn, Optional, Tuple, Union import py_cui import datetime -def _enable_logging(logger, replace_log_file=True, filename='py_cui.log', logging_level=logging.DEBUG): +def _enable_logging(logger: 'PyCUILogger', replace_log_file: bool=True, filename: str='py_cui.log', logging_level=logging.DEBUG) : """Function that creates basic logging configuration for selected logger Parameters @@ -50,8 +52,7 @@ def _enable_logging(logger, replace_log_file=True, filename='py_cui.log', loggin logger.addHandler(log_file) logger.setLevel(logging_level) - -def _initialize_logger(py_cui_root, name=None, custom_logger=True): +def _initialize_logger(py_cui_root: 'py_cui.PyCUI', name: Optional[str]=None, custom_logger: bool=True) : """Function that retrieves an instance of either the default or custom py_cui logger. Parameters @@ -103,14 +104,14 @@ def __init__(self, parent_logger): self._buffer_size = 100 - def print_to_buffer(self, msg, log_level): + def print_to_buffer(self, msg: str, log_level) -> None: """Override of default MenuImplementation add_item function If items override the buffer pop the oldest log message Parameters ---------- - item : str + msg : str Log message to add """ @@ -138,7 +139,7 @@ def __init__(self, parent_logger): self._stop_y = 25 - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base UI element class function. Sets start position relative to entire UI size Returns @@ -152,7 +153,7 @@ def get_absolute_start_pos(self): return start_x, start_y - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base UI element class function. Sets stop position relative to entire UI size Returns @@ -166,7 +167,7 @@ def get_absolute_stop_pos(self): return stop_x, stop_y - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """Override of base class function, handles mouse press in menu Parameters @@ -185,7 +186,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self.set_selected_item_index(elem_clicked) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base class function. Essentially the same as the ScrollMenu widget _handle_key_press, with the exception that Esc breaks @@ -216,7 +217,7 @@ def _handle_key_press(self, key_pressed): self._jump_down(viewport_height) - def _draw(self): + def _draw(self) -> None: """Overrides base class draw function. Mostly a copy of ScrollMenu widget - but reverse item list """ @@ -281,7 +282,7 @@ def draw_live_debug(self): self._live_debug_element._draw() - def _assign_root_window(self, py_cui_root): + def _assign_root_window(self, py_cui_root: 'py_cui.PyCUI') -> None: """Function that assigns a PyCUI root object to the logger. Important for live-debug hooks Parameters @@ -297,7 +298,7 @@ def _assign_root_window(self, py_cui_root): self._live_debug_element.update_height_width() - def _get_debug_text(self, text): + def _get_debug_text(self, text: str) -> str: """Function that generates full debug text for the log Parameters @@ -310,12 +311,13 @@ def _get_debug_text(self, text): msg : str Log message with function, file, and line num info """ - - func = inspect.currentframe().f_back.f_back.f_code + current_frame: Optional['FrameType'] = inspect.currentframe() + if current_frame and current_frame.f_back and current_frame.f_back.f_back is not None: + func = current_frame.f_back.f_back.f_code return f'{text}: Function {func.co_name} in {os.path.basename(func.co_filename)}:{func.co_firstlineno}' - def info(self, text): + def info(self, msg: Any, *args, **kwargs) -> None : # to overcome signature mismatch in error """Override of base logger info function to add hooks for live debug mode Parameters @@ -324,13 +326,13 @@ def info(self, text): The log text ot display """ - debug_text = self._get_debug_text(text) + debug_text = self._get_debug_text(msg) if self.level <= logging.INFO: self._live_debug_element.print_to_buffer(debug_text, 'INFO') super().info(debug_text) - def debug(self, text): + def debug(self, msg: str, *args, **kwargs) -> None: """Override of base logger debug function to add hooks for live debug mode Parameters @@ -339,13 +341,13 @@ def debug(self, text): The log text ot display """ - debug_text = self._get_debug_text(text) + debug_text = self._get_debug_text(msg) if self.level <= logging.DEBUG: self._live_debug_element.print_to_buffer(debug_text, 'DEBUG') super().debug(debug_text) - def warn(self, text): + def warn(self, msg: str, *args, **kwargs) -> None: """Override of base logger warn function to add hooks for live debug mode Parameters @@ -354,13 +356,13 @@ def warn(self, text): The log text ot display """ - debug_text = self._get_debug_text(text) + debug_text = self._get_debug_text(msg) if self.level <= logging.WARN: self._live_debug_element.print_to_buffer(debug_text, 'WARN') super().warn(debug_text) - def error(self, text): + def error(self, msg: str, *args, **kwargs) -> None: """Override of base logger error function to add hooks for live debug mode Parameters @@ -369,13 +371,13 @@ def error(self, text): The log text ot display """ - debug_text = self._get_debug_text(text) + debug_text = self._get_debug_text(msg) if self.level <= logging.ERROR: self._live_debug_element.print_to_buffer(debug_text, 'ERROR') super().error(debug_text) - def critical(self, text): + def critical(self, msg: str, *args, **kwargs) -> None: """Override of base logger critical function to add hooks for live debug mode Parameters @@ -384,7 +386,7 @@ def critical(self, text): The log text ot display """ - debug_text = self._get_debug_text(text) + debug_text = self._get_debug_text(msg) if self.level <= logging.CRITICAL: self._live_debug_element.print_to_buffer(debug_text, 'CRITICAL') super().critical(debug_text) diff --git a/py_cui/dialogs/filedialog.py b/py_cui/dialogs/filedialog.py index 5edfcaa..b636200 100644 --- a/py_cui/dialogs/filedialog.py +++ b/py_cui/dialogs/filedialog.py @@ -1,6 +1,7 @@ """Implementation, widget, and popup classes for file selection dialogs """ +from typing import Optional, Tuple import py_cui.ui import py_cui.widgets import py_cui.popups @@ -12,7 +13,7 @@ import stat -def is_filepath_hidden(path): +def is_filepath_hidden(path: str) -> bool: """Function checks if file or folder is considered "hidden" Parameters @@ -52,7 +53,7 @@ class FileDirElem: icon for file """ - def __init__(self, elem_type, name, fullpath, ascii_icons=False): + def __init__(self, elem_type: str, name: str, fullpath: str, ascii_icons: bool=False): """Intializer for FilDirElem """ @@ -71,7 +72,7 @@ def __init__(self, elem_type, name, fullpath, ascii_icons=False): self._file_icon = ' ' - def get_path(self): + def get_path(self) -> str: """Getter for path Returns @@ -83,7 +84,7 @@ def get_path(self): return self._path - def __str__(self): + def __str__(self) -> str: """Override of to-string function Returns @@ -130,7 +131,7 @@ def __init__(self, initial_loc, dialog_type, ascii_icons, logger, limit_extensio self.refresh_view() - def refresh_view(self): + def refresh_view(self) -> None: """Function that refreshes the current list of files and folders in view """ @@ -202,7 +203,7 @@ def __init__(self, root, initial_dir, dialog_type, ascii_icons, title, color, co #self._run_command_if_none = run_command_if_none - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute start position Returns @@ -217,7 +218,7 @@ def get_absolute_start_pos(self): return start_x, start_y - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute stop position Returns @@ -232,7 +233,7 @@ def get_absolute_stop_pos(self): return stop_x, stop_y - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base handle key press function Enter key runs command, Escape key closes menu @@ -246,24 +247,26 @@ def _handle_key_press(self, key_pressed): super()._handle_key_press(key_pressed) if key_pressed == py_cui.keys.KEY_ENTER: old_dir = self._current_dir - if os.path.isdir(self.get()._path): - self._current_dir = self.get()._path - try: - self.refresh_view() - except FileNotFoundError: - self._parent_dialog.display_warning('Selected directory does not exist!') - self._current_dir = old_dir - self.refresh_view() - except PermissionError: - self._parent_dialog.display_warning(f'Permission Error Accessing: {self._current_dir} !') - self._current_dir = old_dir - self.refresh_view() - finally: - self.set_title(self._current_dir) - elif self._parent_dialog._dialog_type == 'openfile': - self._parent_dialog._submit(self.get()._path) - else: - pass + item = self.get() + if item is not None: + if os.path.isdir(item._path): + self._current_dir = item._path + try: + self.refresh_view() + except FileNotFoundError: + self._parent_dialog.display_warning('Selected directory does not exist!') + self._current_dir = old_dir + self.refresh_view() + except PermissionError: + self._parent_dialog.display_warning(f'Permission Error Accessing: {self._current_dir} !') + self._current_dir = old_dir + self.refresh_view() + finally: + self.set_title(self._current_dir) + elif self._parent_dialog._dialog_type == 'openfile': + self._parent_dialog._submit(item._path) + else: + pass viewport_height = self.get_viewport_height() if key_pressed == py_cui.keys.KEY_UP_ARROW: @@ -280,7 +283,7 @@ def _handle_key_press(self, key_pressed): self._jump_down(viewport_height) - def _draw(self): + def _draw(self) -> None: """Overrides base class draw function """ @@ -331,7 +334,7 @@ def __init__(self, parent_dialog, title, initial_dir, renderer, logger): self.update_height_width() - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute start position Returns @@ -347,7 +350,7 @@ def get_absolute_start_pos(self): return start_x, start_y - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute stop position Returns @@ -363,7 +366,7 @@ def get_absolute_stop_pos(self): return stop_x, stop_y - def update_height_width(self): + def update_height_width(self) -> None: """Override of base class. Updates text field variables for form field """ @@ -380,7 +383,7 @@ def update_height_width(self): self._viewport_width = self._cursor_max_right - self._cursor_max_left - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Handles text input for the field. Called by parent """ @@ -422,7 +425,7 @@ def _handle_key_press(self, key_pressed): self.clear() - def _draw(self): + def _draw(self) -> None: """Draw function for the field. Called from parent. Essentially the same as a TextboxPopup """ @@ -474,7 +477,7 @@ def __init__(self, parent_dialog, statusbar_msg, command, button_num, *args): self._button_num = button_num - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute start position Returns @@ -490,7 +493,7 @@ def get_absolute_start_pos(self): return start_x, start_y - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute stop position Returns @@ -506,7 +509,7 @@ def get_absolute_stop_pos(self): return stop_x, stop_y - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """Handles mouse presses Parameters @@ -524,7 +527,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self.perform_command() - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base class, adds ENTER listener that runs the button's command Parameters @@ -538,7 +541,7 @@ def _handle_key_press(self, key_pressed): self.perform_command() - def perform_command(self): + def perform_command(self) -> None: if self.command is not None: if self._button_num == 1: if self._parent_dialog._dialog_type == 'saveas': @@ -557,7 +560,7 @@ def perform_command(self): self._parent_dialog._root.close_popup() - def _draw(self): + def _draw(self) -> None: """Override of base class draw function """ @@ -587,7 +590,7 @@ def __init__(self, parent, *args): self._parent = parent - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base class, close in parent instead of root """ @@ -664,7 +667,7 @@ def _submit(self, output): self._submit_action(output) - def display_warning(self, message): + def display_warning(self, message: str): """Helper function for showing internal popup warning message Parameters @@ -683,7 +686,7 @@ def display_warning(self, message): self._logger) - def output_valid(self, output): + def output_valid(self, output) -> Tuple[bool,Optional[str]]: if self._dialog_type == 'openfile' and not os.path.isfile(output): return False, 'Please select a valid file path!' elif self._dialog_type == 'opendir' and not os.path.isdir(output): @@ -693,7 +696,7 @@ def output_valid(self, output): return True, None - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions Returns @@ -709,7 +712,7 @@ def get_absolute_start_pos(self): return form_start_x, form_start_y - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions Returns @@ -725,7 +728,7 @@ def get_absolute_stop_pos(self): return form_stop_x, form_stop_y - def update_height_width(self): + def update_height_width(self) -> None: """Override of base class function Also updates all form field elements in the form @@ -741,7 +744,7 @@ def update_height_width(self): pass - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed:int) -> None: """Override of base class. Here, we handle tabs, enters, and escapes All other key presses are passed to the currently selected field element @@ -784,7 +787,7 @@ def _handle_key_press(self, key_pressed): self._internal_popup._handle_key_press(key_pressed) - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """Override of base class function Simply enters the appropriate field when mouse is pressed on it @@ -813,7 +816,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self._cancel_button._handle_mouse_press(x, y, mouse_event) - def _draw(self): + 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 197f98f..da23b76 100644 --- a/py_cui/dialogs/form.py +++ b/py_cui/dialogs/form.py @@ -1,6 +1,7 @@ """Form widget for py_cui. Allows for giving user several fillable text fields in one block """ +from typing import Any, Callable, Dict, List, Optional, Tuple import py_cui.ui import py_cui.widgets import py_cui.popups @@ -24,7 +25,7 @@ class FormField(py_cui.ui.TextBoxImplementation): Toggle for making the field be required """ - def __init__(self, fieldname, initial_text, password, required, logger): + def __init__(self, fieldname: str, initial_text: str, password: bool, required: bool, logger): """Initializer for base FormFields """ @@ -33,7 +34,7 @@ def __init__(self, fieldname, initial_text, password, required, logger): self._required = required - def get_fieldname(self): + def get_fieldname(self) -> str: """Getter for field name Returns @@ -45,7 +46,7 @@ def get_fieldname(self): return self._fieldname - def is_valid(self): + def is_valid(self) -> Tuple[bool,Optional[str]]: """Function that checks if field is valid. This function can be implemented by subclasses to support different @@ -66,7 +67,7 @@ def is_valid(self): return msg is None, msg - def is_required(self): + def is_required(self) -> bool: """Checks if field is required Returns @@ -89,7 +90,7 @@ class FormFieldElement(py_cui.ui.UIElement, FormField): The parent UI Element that contains the form element """ - def __init__(self, parent_form, field_index, field, init_text, passwd, required, renderer, logger): + def __init__(self, parent_form, field_index: int, field, init_text: str, passwd: bool, required: bool, renderer: 'py_cui.renderer.Renderer', logger): """Initializer for the FormFieldElement class """ @@ -104,7 +105,7 @@ def __init__(self, parent_form, field_index, field, init_text, passwd, required, self.update_height_width() - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute start position Returns @@ -121,7 +122,7 @@ def get_absolute_start_pos(self): return field_start_x, field_start_y - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base function. Uses the parent element do compute stop position Returns @@ -139,7 +140,7 @@ def get_absolute_stop_pos(self): return field_stop_x, field_stop_y - def update_height_width(self): + def update_height_width(self) -> None: """Override of base class. Updates text field variables for form field """ @@ -155,7 +156,7 @@ def update_height_width(self): self._viewport_width = self._cursor_max_right - self._cursor_max_left - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Handles text input for the field. Called by parent """ @@ -217,7 +218,7 @@ class FormImplementation(py_cui.ui.UIImplementation): Function fired when submit is called """ - def __init__(self, field_implementations, required_fields, logger): + def __init__(self, field_implementations: List['FormField'], required_fields: List[str], logger): """Initializer for the FormImplemnentation class """ @@ -226,10 +227,10 @@ def __init__(self, field_implementations, required_fields, logger): self._required_fields = required_fields self._selected_form_index = 0 - self._on_submit_action = None + self._on_submit_action: Optional[Callable[[],Any]] = None - def get_selected_form_index(self): + def get_selected_form_index(self) -> int: """Getter for selected form index Returns @@ -240,7 +241,7 @@ def get_selected_form_index(self): return self._selected_form_index - def set_selected_form_index(self, form_index): + def set_selected_form_index(self, form_index: int) -> None: """Setter for selected form index Parameters @@ -252,7 +253,7 @@ def set_selected_form_index(self, form_index): self._selected_form_index = form_index - def set_on_submit_action(self, on_submit_action): + def set_on_submit_action(self, on_submit_action: Callable[[],Any]): """Setter for callback on submit Parameters @@ -264,7 +265,7 @@ def set_on_submit_action(self, on_submit_action): self._on_submit_action = on_submit_action - def jump_to_next_field(self): + def jump_to_next_field(self) -> None: """Function used to jump between form fields """ @@ -274,7 +275,7 @@ def jump_to_next_field(self): self.set_selected_form_index(0) - def is_submission_valid(self): + def is_submission_valid(self) -> Tuple[bool,Optional[str]]: """Function that checks if all fields are filled out correctly Returns @@ -292,7 +293,7 @@ def is_submission_valid(self): return True, None - def get(self): + def get(self) -> Dict[str,str]: """Gets values entered into field as a dictionary Returns @@ -323,7 +324,7 @@ class InternalFormPopup(py_cui.popups.MessagePopup): The parent form popup that spawned the message popup """ - def __init__(self, parent, *args): + def __init__(self, parent: 'FormPopup', *args): """Initializer for Internal form Popup """ @@ -331,7 +332,7 @@ def __init__(self, parent, *args): self._parent = parent - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base class, close in parent instead of root """ @@ -360,7 +361,7 @@ def __init__(self, root, fields, passwd_fields, required_fields, fields_init_tex py_cui.popups.Popup.__init__(self, root, title, '', color, renderer, logger) - self._form_fields = [] + self._form_fields: List['FormFieldElement'] = [] for i, field in enumerate(fields): init_text = '' if field in fields_init_text: @@ -379,7 +380,7 @@ def __init__(self, root, fields, passwd_fields, required_fields, fields_init_tex self._internal_popup = None - def get_num_fields(self): + def get_num_fields(self) -> int: """Getter for number of fields Returns @@ -391,7 +392,7 @@ def get_num_fields(self): return self._num_fields - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions Returns @@ -417,7 +418,7 @@ def get_absolute_start_pos(self): return form_start_x, form_start_y - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions Returns @@ -443,7 +444,7 @@ def get_absolute_stop_pos(self): return form_stop_x, form_stop_y - def update_height_width(self): + def update_height_width(self) -> None: """Override of base class function Also updates all form field elements in the form @@ -457,7 +458,7 @@ def update_height_width(self): pass - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base class. Here, we handle tabs, enters, and escapes All other key presses are passed to the currently selected field element @@ -477,7 +478,8 @@ def _handle_key_press(self, key_pressed): valid, err_msg = self.is_submission_valid() if valid: self._root.close_popup() - self._on_submit_action(self.get()) + if self._on_submit_action is not None: + self._on_submit_action(self.get()) #on_submit_action is a no-arg function? else: self._internal_popup = InternalFormPopup(self, self._root, @@ -495,7 +497,7 @@ def _handle_key_press(self, key_pressed): self._internal_popup._handle_key_press(key_pressed) - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """Override of base class function Simply enters the appropriate field when mouse is pressed on it @@ -515,7 +517,7 @@ def _handle_mouse_press(self, x, y, mouse_event): break - def _draw(self): + 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/grid.py b/py_cui/grid.py index f2cba4d..640bff6 100644 --- a/py_cui/grid.py +++ b/py_cui/grid.py @@ -7,6 +7,7 @@ # Created: 12-Aug-2019 +from typing import Tuple import py_cui @@ -30,7 +31,7 @@ class Grid: """ - def __init__(self, num_rows, num_columns, height, width, logger): + def __init__(self, num_rows: int, num_columns: int, height: int, width: int, logger: 'py_cui.debug.PyCUILogger'): """Constructor for the Grid class Parameters @@ -57,7 +58,7 @@ def __init__(self, num_rows, num_columns, height, width, logger): self._logger = logger - def get_dimensions(self): + def get_dimensions(self) -> Tuple[int,int]: """Gets dimensions in rows/columns Returns @@ -71,7 +72,7 @@ def get_dimensions(self): return self._num_rows, self._num_columns - def get_dimensions_absolute(self): + def get_dimensions_absolute(self) -> Tuple[int,int]: """Gets dimensions of grid in terminal characters Returns @@ -85,7 +86,7 @@ def get_dimensions_absolute(self): return self._height, self._width - def get_offsets(self): + def get_offsets(self) -> Tuple[int,int]: """Gets leftover characters for x and y Returns @@ -99,7 +100,7 @@ def get_offsets(self): return self._offset_x, self._offset_y - def get_cell_dimensions(self): + def get_cell_dimensions(self) -> Tuple[int,int]: """Gets size in characters of single (row, column) cell location Returns @@ -113,7 +114,7 @@ def get_cell_dimensions(self): return self._row_height, self._column_width - def set_num_rows(self, num_rows): + def set_num_rows(self, num_rows: int) -> None: """Sets the grid row size Parameters @@ -134,7 +135,7 @@ def set_num_rows(self, num_rows): self._row_height = int(self._height / self._num_rows) - def set_num_cols(self, num_columns): + def set_num_cols(self, num_columns: int) -> None: """Sets the grid column size Parameters @@ -156,7 +157,7 @@ def set_num_cols(self, num_columns): self._column_width = int(self._width / self._num_columns) - def update_grid_height_width(self, height, width): + def update_grid_height_width(self, height: int, width: int): """Update grid height and width. Allows for on-the-fly size editing Parameters diff --git a/py_cui/keys.py b/py_cui/keys.py index 58ffb1b..af6fb00 100644 --- a/py_cui/keys.py +++ b/py_cui/keys.py @@ -7,10 +7,11 @@ from sys import platform import curses +from typing import Optional # Some simple helper functions -def get_ascii_from_char(char): +def get_ascii_from_char(char: str) -> int: """Function that converts ascii code to character Parameters @@ -27,7 +28,7 @@ def get_ascii_from_char(char): return ord(char) -def get_char_from_ascii(key_num): +def get_char_from_ascii(key_num: int) -> Optional[str]: """Function that converts a character to an ascii code Parameters diff --git a/py_cui/popups.py b/py_cui/popups.py index 097faaf..46ed9ec 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -7,6 +7,7 @@ # required library imports import curses +from typing import Tuple import py_cui import py_cui.ui import py_cui.errors @@ -32,7 +33,7 @@ class Popup(py_cui.ui.UIElement): """ - def __init__(self, root, title, text, color, renderer, logger): + def __init__(self, root: 'py_cui.PyCUI', title: str, text: str, color: int, renderer: 'py_cui.renderer.Renderer', logger): """Initializer for main popup class. Calls UIElement intialier, and sets some initial values """ @@ -56,7 +57,7 @@ def _increment_counter(self): pass - def set_text(self, text): + def set_text(self, text: str) -> None : """Sets popup text (message) Parameters @@ -68,7 +69,7 @@ def set_text(self, text): self._text = text - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions Returns @@ -81,7 +82,7 @@ def get_absolute_start_pos(self): return int(root_width / 4), int(root_height / 3) - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Override of base class, computes position based on root dimensions Returns @@ -94,7 +95,7 @@ def get_absolute_stop_pos(self): return (int(3 * root_width / 4)), (int(2 * root_height / 3)) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Handles key presses when popup is open By default, only closes popup when Escape is pressed @@ -109,7 +110,7 @@ def _handle_key_press(self, key_pressed): self._root.close_popup() - def _draw(self): + def _draw(self) -> None: """Function that uses renderer to draw the popup Can be implemented by subclass. Base draw function will draw the title and text in a bordered box @@ -144,7 +145,7 @@ def __init__(self, root, title, text, color, renderer, logger): py_cui.keys.KEY_DELETE] - def _draw(self): + def _draw(self) -> None: """Draw function for MessagePopup. Calls superclass draw() """ @@ -168,7 +169,7 @@ def __init__(self, root, title, text, color, command, renderer, logger): self._command = command - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int): """Handle key press overwrite from superclass Parameters @@ -220,7 +221,7 @@ def __init__(self, root, title, color, command, renderer, password, logger): self.update_height_width() - def update_height_width(self): + def update_height_width(self) -> None: """Need to update all cursor positions on resize """ @@ -236,7 +237,7 @@ def update_height_width(self): self._viewport_width = self._cursor_max_right - self._cursor_max_left - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event) -> None: """Override of base class function, handles mouse press in menu Parameters @@ -257,7 +258,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self._cursor_text_pos = len(self._text) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int): """Override of base handle key press function Parameters @@ -295,7 +296,7 @@ def _handle_key_press(self, key_pressed): self._insert_char(key_pressed) - def _draw(self): + def _draw(self) -> None: """Override of base draw function """ @@ -335,7 +336,7 @@ class MenuPopup(Popup, py_cui.ui.MenuImplementation): Runs command even if there are no menu items (passes None) """ - def __init__(self, root, items, title, color, command, renderer, logger, run_command_if_none): + def __init__(self, root: 'py_cui.PyCUI', items, title, color, command, renderer, logger, run_command_if_none): """Initializer for MenuPopup. Uses MenuImplementation as base """ @@ -346,7 +347,7 @@ def __init__(self, root, items, title, color, command, renderer, logger, run_com self._run_command_if_none = run_command_if_none - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event): """Override of base class function, handles mouse press in menu Parameters @@ -367,12 +368,12 @@ def _handle_mouse_press(self, x, y, mouse_event): # For double clicks we also process the menu selection if mouse_event == py_cui.keys.LEFT_MOUSE_DBL_CLICK: ret_val = self.get() - self.root.close_popup() + self._root.close_popup() self._command(ret_val) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base handle key press function Enter key runs command, Escape key closes menu @@ -407,7 +408,7 @@ def _handle_key_press(self, key_pressed): self._scroll_down(viewport_height) - def _draw(self): + def _draw(self) -> None: """Overrides base class draw function """ @@ -457,7 +458,7 @@ def __init__(self, root, title, message, color, renderer, logger): self._message = message - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int): """Override of base class function. Loading icon popups cannot be cancelled, so we wish to avoid default behavior @@ -471,7 +472,7 @@ def _handle_key_press(self, key_pressed): pass - def _draw(self): + def _draw(self) -> None: """Overrides base draw function """ @@ -508,7 +509,7 @@ def __init__(self, root, title, num_items, color, renderer, logger): self._completed_items = 0 - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int): """Override of base class function. Loading icon popups cannot be cancelled, so we wish to avoid default behavior @@ -521,14 +522,14 @@ def _handle_key_press(self, key_pressed): pass - def _increment_counter(self): + def _increment_counter(self) -> None: """Function that increments an internal counter """ self._completed_items += 1 - def _draw(self): + def _draw(self) -> None: """Override of base draw function """ diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 28aa6e0..1c47daf 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -9,10 +9,15 @@ # 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 import py_cui.widgets as widgets import py_cui.grid as grid import py_cui.controls as controls +if TYPE_CHECKING: + import py_cui + import py_cui.widgets + class WidgetSet: """Class that represents a collection of widgets. @@ -33,11 +38,11 @@ class WidgetSet: Main PyCUI object reference """ - def __init__(self, num_rows, num_cols, logger, root, simulated_terminal=None): + def __init__(self, num_rows: int, num_cols: int, logger: 'py_cui.debug.PyCUILogger', root:'py_cui.PyCUI', simulated_terminal: Optional[List[int]] =None): """Constructor for WidgetSet """ - self._widgets = {} + self._widgets: Dict[int,Optional['py_cui.widgets.Widget']] = {} self._keybindings = {} self._root = root @@ -58,11 +63,11 @@ def __init__(self, num_rows, num_cols, logger, root, simulated_terminal=None): self._grid = grid.Grid(num_rows, num_cols, self._height, self._width, logger) - self._selected_widget = None + self._selected_widget: Optional[int] = None self._logger = logger - def set_selected_widget(self, widget_id): + def set_selected_widget(self, widget_id: int) -> None: """Function that sets the selected cell for the CUI Parameters @@ -75,7 +80,7 @@ def set_selected_widget(self, widget_id): self._selected_widget = widget_id - def get_widgets(self): + def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']]: """Function that gets current set of widgets Returns @@ -87,7 +92,7 @@ def get_widgets(self): return self._widgets - def add_key_command(self, key, command): + def add_key_command(self, key: int, command: Callable[[],Any]): """Function that adds a keybinding to the CUI when in overview mode Parameters @@ -101,7 +106,7 @@ def add_key_command(self, key, command): self._keybindings[key] = command - def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0): + 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 @@ -145,7 +150,7 @@ def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, pad return new_scroll_menu - def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx=1, pady=0, checked_char='X'): + 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 @@ -192,7 +197,7 @@ def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx= return new_checkbox_menu - def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = '', password = False): + 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 @@ -240,7 +245,7 @@ def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = return new_text_box - def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = ''): + 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 @@ -287,7 +292,7 @@ def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx return new_text_block - def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0): + 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 @@ -329,7 +334,7 @@ def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, return new_label - def add_block_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, center=True): + 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 @@ -374,7 +379,7 @@ def add_block_label(self, title, row, column, row_span = 1, column_span = 1, pad return new_label - def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, command=None): + 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 @@ -421,9 +426,11 @@ def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1 return new_button - def add_slider(self, title, row, column, row_span=1, - column_span=1, padx=1, pady=0, - min_val=0, max_val=100, step=1, init_val=0): + 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 @@ -458,7 +465,7 @@ def add_slider(self, title, row, column, row_span=1, A reference to the created slider object. """ - id = f'Widget{len(self._widgets.keys())}' + id = len(self._widgets.keys()) new_slider = controls.slider.SliderWidget(id, title, self._grid, diff --git a/py_cui/widgets.py b/py_cui/widgets.py index fd606b8..6d4e3bf 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -459,7 +459,7 @@ class ScrollMenu(Widget, py_cui.ui.MenuImplementation): """A scroll menu widget. """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger): + def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger: 'py_cui.debug.PyCUILogger'): """Initializer for scroll menu. calls superclass initializers and sets help text """ From 37081624f9dfbe83b5373fc2f0a33cc51b0e2c90 Mon Sep 17 00:00:00 2001 From: mohan-cloud Date: Mon, 21 Jun 2021 14:33:38 +0000 Subject: [PATCH 29/40] wip --- py_cui/controls/slider.py | 4 +- py_cui/widget_set.py | 4 +- py_cui/widgets.py | 102 +++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/py_cui/controls/slider.py b/py_cui/controls/slider.py index 4f618aa..b4b742c 100644 --- a/py_cui/controls/slider.py +++ b/py_cui/controls/slider.py @@ -6,7 +6,7 @@ class SliderImplementation(py_cui.ui.UIImplementation): - def __init__(self, min_val, max_val, init_val, step, logger): + def __init__(self, min_val: int, max_val: int, init_val: int, step: int, logger): super().__init__(logger) self._min_val = min_val @@ -21,7 +21,7 @@ def __init__(self, min_val, max_val, init_val, step, logger): f'initial value must be between {self._min_val} and {self._max_val}') - def set_bar_char(self, char: str): + def set_bar_char(self, char: str) -> None: """ Updates the character used to represent the slider bar. diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 1c47daf..9b6e1a8 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -43,7 +43,7 @@ def __init__(self, num_rows: int, num_cols: int, logger: 'py_cui.debug.PyCUILogg """ self._widgets: Dict[int,Optional['py_cui.widgets.Widget']] = {} - self._keybindings = {} + self._keybindings: Dict[int,Callable[[],Any]] = {} self._root = root self._simulated_terminal = simulated_terminal @@ -379,7 +379,7 @@ def add_block_label(self, title: str, row: int, column: int, row_span: int = 1, 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: Optional[Callable[[],Any]]=None) -> 'py_cui.widgets.Button': """Function that adds a new button to the CUI grid Parameters diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 6d4e3bf..b55d084 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -23,7 +23,7 @@ import curses import inspect -from typing import Callable +from typing import Callable, List, Dict, Tuple, Any, Optional import py_cui import py_cui.ui import py_cui.colors @@ -52,7 +52,7 @@ class Widget(py_cui.ui.UIElement): color rules to load into renderer when drawing widget """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, selectable = True): + 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, selectable: bool = True): """Initializer for base widget class Class UIElement superclass initializer, and then assigns widget to grid, along with row/column info @@ -75,15 +75,15 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self._padx = padx self._pady = pady self._selectable = selectable - self._key_commands = {} - self._mouse_commands = {} - self._text_color_rules = [] + self._key_commands: Dict[int,Callable[[],Any]] = {} + self._mouse_commands: Dict[int,Callable[[],Any]] = {} + self._text_color_rules: List['py_cui.ColorRule'] = [] self._default_color = py_cui.WHITE_ON_BLACK self._border_color = self._default_color self.update_height_width() - def add_key_command(self, key, command): + 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 Parameters @@ -97,7 +97,7 @@ def add_key_command(self, key, command): self._key_commands[key] = command - def add_mouse_command(self, mouse_event, command): + def add_mouse_command(self, mouse_event: int, command: Callable[[],Any]) -> None: """Maps a keycode to a function that will be executed when in focus mode Parameters @@ -122,7 +122,7 @@ def add_mouse_command(self, mouse_event, command): self._mouse_commands[mouse_event] = command - def update_key_command(self, key, command): + def update_key_command(self, key: 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 @@ -137,7 +137,7 @@ def update_key_command(self, key, command): self.add_key_command(key, command) - def add_text_color_rule(self, regex, color, rule_type, match_type='line', region=[0,1], include_whitespace=False, selected_color=None): + def add_text_color_rule(self, regex: str, color: int, rule_type: str, match_type: str='line', region: List[int]=[0,1], include_whitespace: bool=False, selected_color=None) -> None: """Forces renderer to draw text using given color if text_condition_function returns True Parameters @@ -164,7 +164,7 @@ def add_text_color_rule(self, regex, color, rule_type, match_type='line', region self._text_color_rules.append(new_color_rule) - def get_absolute_start_pos(self): + def get_absolute_start_pos(self) -> Tuple[int,int]: """Gets the absolute position of the widget in characters. Override of base class function Returns @@ -188,7 +188,7 @@ def get_absolute_start_pos(self): return x_pos, y_pos - def get_absolute_stop_pos(self): + def get_absolute_stop_pos(self) -> Tuple[int,int]: """Gets the absolute dimensions of the widget in characters. Override of base class function Returns @@ -216,7 +216,7 @@ def get_absolute_stop_pos(self): return width + self._start_x, height + self._start_y - def get_grid_cell(self): + def get_grid_cell(self) -> Tuple[int,int]: """Gets widget row, column in grid Returns @@ -228,7 +228,7 @@ def get_grid_cell(self): return self._row, self._column - def get_grid_cell_spans(self): + def get_grid_cell_spans(self) -> Tuple[int,int]: """Gets widget row span, column span in grid Returns @@ -240,7 +240,7 @@ def get_grid_cell_spans(self): return self._row_span, self._column_span - def set_selectable(self, selectable): + def set_selectable(self, selectable: bool) -> None: """Setter for widget selectablility Paramters @@ -252,7 +252,7 @@ def set_selectable(self, selectable): self._selectable = selectable - def is_selectable(self): + def is_selectable(self) -> bool: """Checks if the widget is selectable Returns @@ -264,7 +264,7 @@ def is_selectable(self): return self._selectable - def _is_row_col_inside(self, row, col): + def _is_row_col_inside(self, row: int, col: int) -> bool: """Checks if a particular row + column is inside the widget area Parameters @@ -290,7 +290,7 @@ def _is_row_col_inside(self, row, col): # BELOW FUNCTIONS SHOULD BE OVERWRITTEN BY SUB-CLASSES - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int): """Base class function that handles all assigned mouse presses. When overwriting this function, make sure to add a super()._handle_mouse_press(x, y, mouse_event) call, @@ -324,7 +324,7 @@ def _handle_mouse_press(self, x, y, mouse_event): command() - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Base class function that handles all assigned key presses. When overwriting this function, make sure to add a super()._handle_key_press(key_pressed) call, @@ -341,7 +341,7 @@ def _handle_key_press(self, key_pressed): command() - def _draw(self): + def _draw(self) -> None: """Base class draw class that checks if renderer is valid. Should be called with super()._draw() in overrides. @@ -365,7 +365,7 @@ class Label(Widget): Toggle for drawing label border """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, 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): """Initalizer for Label widget """ @@ -373,14 +373,14 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, p self._draw_border = False - def toggle_border(self): + def toggle_border(self) -> None: """Function that gives option to draw border around label """ self._draw_border = not self._draw_border - def _draw(self): + def _draw(self) -> None: """Override base draw class. Center text and draw it @@ -406,7 +406,7 @@ class BlockLabel(Widget): Decides whether or not label should be centered """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, 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, center, logger): """Initializer for blocklabel widget """ @@ -416,7 +416,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, p self._draw_border = False - def set_title(self, title): + def set_title(self, title: str) -> None: """Override of base class, splits title into lines for rendering line by line. Parameters @@ -429,14 +429,14 @@ def set_title(self, title): self._lines = title.splitlines() - def toggle_border(self): + def toggle_border(self) -> None: """Function that gives option to draw border around label """ self._draw_border = not self._draw_border - def _draw(self): + def _draw(self) -> None: """Override base draw class. Center text and draw it @@ -459,17 +459,17 @@ class ScrollMenu(Widget, py_cui.ui.MenuImplementation): """A scroll menu widget. """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger: 'py_cui.debug.PyCUILogger'): + 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'): """Initializer for scroll menu. calls superclass initializers and sets help text """ Widget.__init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger) py_cui.ui.MenuImplementation.__init__(self, logger) - self._on_selection_change = None + self._on_selection_change: Optional[Callable[[Any],Any]] = None self.set_help_text('Focus mode on ScrollMenu. Use Up/Down/PgUp/PgDown/Home/End to scroll, Esc to exit.') - def set_on_selection_change_event(self, on_selection_change_event): + def set_on_selection_change_event(self, on_selection_change_event: Callable[[Any],Any]): """Function that sets the function fired when menu selection changes, with the new selection as an arg Parameters @@ -482,14 +482,14 @@ def set_on_selection_change_event(self, on_selection_change_event): TypeError Raises a type error if event function is not callable """ - - if not isinstance(on_selection_change_event, Callable): + #mypy false-positive + if not isinstance(on_selection_change_event, Callable): #type: ignore raise TypeError('On selection change event must be a Callable!') self._on_selection_change = on_selection_change_event - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int): """Override of base class function, handles mouse press in menu Parameters @@ -516,7 +516,7 @@ def _handle_mouse_press(self, x, y, mouse_event): - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override base class function. UP_ARROW scrolls up, DOWN_ARROW scrolls down. @@ -548,7 +548,7 @@ def _handle_key_press(self, key_pressed): self._on_selection_change(self.get()) - def _draw(self): + def _draw(self) -> None: """Overrides base class draw function """ @@ -585,7 +585,7 @@ class CheckBoxMenu(Widget, py_cui.ui.CheckBoxMenuImplementation): Character to represent a checked item """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, checked_char): + 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, checked_char: str): """Initializer for CheckBoxMenu Widget """ @@ -594,7 +594,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.set_help_text('Focus mode on CheckBoxMenu. Use up/down to scroll, Enter to toggle set, unset, Esc to exit.') - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """Override of base class function, handles mouse press in menu Parameters @@ -611,7 +611,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self.mark_item_as_checked(self._view_items[elem_clicked]) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of key presses. First, run the superclass function, scrolling should still work. @@ -641,7 +641,7 @@ def _handle_key_press(self, key_pressed): self.mark_item_as_checked(self.get()) - def _draw(self): + def _draw(self) -> None: """Overrides base class draw function """ @@ -682,7 +682,7 @@ class Button(Widget): A no-args function to run when the button is pressed. """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, command): + 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, command: Optional[Callable[[],Any]]): """Initializer for Button Widget """ @@ -697,7 +697,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.add_mouse_command(py_cui.keys.LEFT_MOUSE_DBL_CLICK, self.command) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base class, adds ENTER listener that runs the button's command Parameters @@ -712,7 +712,7 @@ def _handle_key_press(self, key_pressed): return self.command() - def _draw(self): + def _draw(self) -> None: """Override of base class draw function """ @@ -730,7 +730,7 @@ class TextBox(Widget, py_cui.ui.TextBoxImplementation): """Widget for entering small single lines of text """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, initial_text, password): + 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, initial_text: str, password: bool): """Initializer for TextBox widget. Uses TextBoxImplementation as base """ @@ -740,7 +740,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.set_help_text('Focus mode on TextBox. Press Esc to exit focus mode.') - def update_height_width(self): + def update_height_width(self) -> None: """Need to update all cursor positions on resize """ @@ -757,7 +757,7 @@ def update_height_width(self): self._viewport_width = self._cursor_max_right - self._cursor_max_left - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """Override of base class function, handles mouse press in menu Parameters @@ -778,7 +778,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self._cursor_text_pos = len(self._text) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base handle key press function Parameters @@ -805,7 +805,7 @@ def _handle_key_press(self, key_pressed): self._insert_char(key_pressed) - def _draw(self): + def _draw(self) -> None: """Override of base draw function """ @@ -837,7 +837,7 @@ class ScrollTextBlock(Widget, py_cui.ui.TextBlockImplementation): """Widget for editing large multi-line blocks of text """ - def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, initial_text): + 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, initial_text: str): """Initializer for TextBlock Widget. Uses TextBlockImplementation as base """ @@ -847,7 +847,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pa self.set_help_text('Focus mode on TextBlock. Press Esc to exit focus mode.') - def update_height_width(self): + def update_height_width(self) -> None: """Function that updates the position of the text and cursor on resize """ @@ -866,7 +866,7 @@ def update_height_width(self): self._viewport_height = self._cursor_max_down - self._cursor_max_up - def _handle_mouse_press(self, x, y, mouse_event): + def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None: """Override of base class function, handles mouse press in menu Parameters @@ -900,7 +900,7 @@ def _handle_mouse_press(self, x, y, mouse_event): self._cursor_text_pos_x = len(line) - def _handle_key_press(self, key_pressed): + def _handle_key_press(self, key_pressed: int) -> None: """Override of base class handle key press function Parameters @@ -937,7 +937,7 @@ def _handle_key_press(self, key_pressed): self._insert_char(key_pressed) - def _draw(self): + def _draw(self) -> None: """Override of base class draw function """ From b91534cc2cf2767e119d9c5cb505e87a71190ee8 Mon Sep 17 00:00:00 2001 From: mohan-cloud Date: Mon, 21 Jun 2021 15:26:43 +0000 Subject: [PATCH 30/40] clean up --- py_cui/__init__.py | 14 +++++++------- py_cui/colors.py | 4 ++-- py_cui/debug.py | 5 ++--- py_cui/dialogs/filedialog.py | 12 ++++++------ py_cui/dialogs/form.py | 4 ++-- py_cui/popups.py | 2 +- py_cui/renderer.py | 6 ++---- py_cui/statusbar.py | 2 +- py_cui/ui.py | 12 ++++++------ py_cui/widget_set.py | 1 - py_cui/widgets.py | 3 ++- 11 files changed, 31 insertions(+), 34 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 9e9c17a..e291c0c 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -382,7 +382,7 @@ def _initialize_widget_renderer(self) -> None: if self._renderer is None: 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] # using a temporary variable so that mypy doesn't show error + widget = self.get_widgets()[widget_id] if widget is not None: try: widget._assign_renderer(self._renderer) @@ -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']]: # docstring to be updated? + def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']]: """Function that gets current set of widgets Returns ------- - widgets : dict of str -> widget # dict of int -> widget + widgets : dict of int -> widget dictionary mapping widget IDs to object instances """ @@ -843,7 +843,7 @@ def add_slider(self, title: str, row: int, column: int, row_span: int=1, 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 @@ -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] # using temp 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: id_list.append(widget_id) @@ -1005,7 +1005,7 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: Returns ------- - widget_id : str # returns int after commit 936dee1 + 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 : str #to be changed + widget_id : int the id of the widget to select """ diff --git a/py_cui/colors.py b/py_cui/colors.py index bb1cf07..667515c 100644 --- a/py_cui/colors.py +++ b/py_cui/colors.py @@ -5,10 +5,10 @@ # Created: 12-Aug-2019 -from typing import List, Tuple, Union import py_cui import curses import re +from typing import List, Tuple, Union # Curses color configuration - curses colors automatically work as pairs, so it was easiest to @@ -345,7 +345,7 @@ def generate_fragments(self, widget: Union['py_cui.widgets.Widget','py_cui.ui.UI fragments: List[List[Union[int,str]]] = [] match = self._check_match(line) if selected: - fragments = [[render_text, widget.get_selected_color()]] # tuple might be better? + fragments = [[render_text, widget.get_selected_color()]] else: fragments = [[render_text, widget.get_color()]] diff --git a/py_cui/debug.py b/py_cui/debug.py index 532a33a..08d1248 100644 --- a/py_cui/debug.py +++ b/py_cui/debug.py @@ -7,10 +7,9 @@ import os import logging import inspect -from types import FrameType -from typing import Any, NoReturn, Optional, Tuple, Union import py_cui import datetime +from typing import Any, Optional, Tuple def _enable_logging(logger: 'PyCUILogger', replace_log_file: bool=True, filename: str='py_cui.log', logging_level=logging.DEBUG) : @@ -311,7 +310,7 @@ def _get_debug_text(self, text: str) -> str: msg : str Log message with function, file, and line num info """ - current_frame: Optional['FrameType'] = inspect.currentframe() + current_frame = inspect.currentframe() if current_frame and current_frame.f_back and current_frame.f_back.f_back is not None: func = current_frame.f_back.f_back.f_code return f'{text}: Function {func.co_name} in {os.path.basename(func.co_filename)}:{func.co_firstlineno}' diff --git a/py_cui/dialogs/filedialog.py b/py_cui/dialogs/filedialog.py index b636200..0b5b46c 100644 --- a/py_cui/dialogs/filedialog.py +++ b/py_cui/dialogs/filedialog.py @@ -1,12 +1,12 @@ """Implementation, widget, and popup classes for file selection dialogs """ -from typing import Optional, Tuple import py_cui.ui import py_cui.widgets import py_cui.popups import py_cui.colors import os +from typing import List, Optional, Tuple # Imports used to detect hidden files import sys @@ -116,7 +116,7 @@ class FileSelectImplementation(py_cui.ui.MenuImplementation): """ - def __init__(self, initial_loc, dialog_type, ascii_icons, logger, limit_extensions = [], show_hidden=False): + def __init__(self, initial_loc: str, dialog_type: str, ascii_icons, logger, limit_extensions: List[str] = [], show_hidden: bool=False): """Initalizer for the file select menu implementation. Includes some logic for getting list of file and folders. """ @@ -182,7 +182,7 @@ class FileSelectElement(py_cui.ui.UIElement, FileSelectImplementation): Runs command even if there are no menu items (passes None) """ - def __init__(self, root, initial_dir, dialog_type, ascii_icons, title, color, command, renderer, logger, limit_extensions=[]): + def __init__(self, root, initial_dir, dialog_type: str, ascii_icons, title, color, command, renderer, logger, limit_extensions: List[str]=[]): """Initializer for MenuPopup. Uses MenuImplementation as base """ @@ -659,15 +659,15 @@ def __init__(self, root, callback, initial_dir, dialog_type, ascii_icons, limit_ self._currently_selected = self._file_dir_select - def _submit(self, output): + def _submit(self, output: str) -> None: valid, msg = self.output_valid(output) - if not valid: + if not valid and msg is not None: self.display_warning(msg) else: self._submit_action(output) - def display_warning(self, message: str): + def display_warning(self, message: str) -> None: """Helper function for showing internal popup warning message Parameters diff --git a/py_cui/dialogs/form.py b/py_cui/dialogs/form.py index da23b76..af641ad 100644 --- a/py_cui/dialogs/form.py +++ b/py_cui/dialogs/form.py @@ -176,7 +176,7 @@ def _handle_key_press(self, key_pressed: int) -> None: self._insert_char(key_pressed) - def _draw(self): + def _draw(self) -> None: """Draw function for the field. Called from parent. Essentially the same as a TextboxPopup """ @@ -479,7 +479,7 @@ 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()) #on_submit_action is a no-arg function? + self._on_submit_action(self.get()) else: self._internal_popup = InternalFormPopup(self, self._root, diff --git a/py_cui/popups.py b/py_cui/popups.py index 46ed9ec..e3fa63f 100644 --- a/py_cui/popups.py +++ b/py_cui/popups.py @@ -7,10 +7,10 @@ # required library imports import curses -from typing import Tuple import py_cui import py_cui.ui import py_cui.errors +from typing import Tuple class Popup(py_cui.ui.UIElement): diff --git a/py_cui/renderer.py b/py_cui/renderer.py index 6b86d7f..ac3b78c 100644 --- a/py_cui/renderer.py +++ b/py_cui/renderer.py @@ -7,10 +7,8 @@ import curses import py_cui import py_cui.colors -from typing import Any, Dict, Tuple, List, TYPE_CHECKING, Union, cast +from typing import Dict, List, Union -if TYPE_CHECKING: - import py_cui.ui class Renderer: @@ -310,7 +308,7 @@ def _get_render_text(self, ui_element: 'py_cui.ui.UIElement', line: str, centere else: render_text = line[start_pos:start_pos + render_text_length] - render_text_fragments = self._generate_text_color_fragments(ui_element, line, render_text, selected) # returns List[List[int,str]] + render_text_fragments = self._generate_text_color_fragments(ui_element, line, render_text, selected) return render_text_fragments diff --git a/py_cui/statusbar.py b/py_cui/statusbar.py index 8fc043a..eeb62fc 100644 --- a/py_cui/statusbar.py +++ b/py_cui/statusbar.py @@ -22,7 +22,7 @@ class StatusBar: Is the StatusBar displayed on the top of the grid """ - def __init__(self, text: str, color: int, root: 'py_cui.PyCUI', is_title_bar=False): + def __init__(self, text: str, color: int, root: 'py_cui.PyCUI', is_title_bar: bool=False): """Initializer for statusbar """ diff --git a/py_cui/ui.py b/py_cui/ui.py index aa4fbb7..5833a52 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -7,10 +7,10 @@ # Created: 19-Mar-2020 -from typing import Any, List, NoReturn, Optional, Tuple, Union import py_cui import py_cui.errors import py_cui.colors +from typing import Any, List, Optional, Tuple class UIElement: @@ -682,7 +682,7 @@ def __init__(self, logger): self._view_items = [] - def clear(self): + def clear(self) -> None: """Clears all items from the Scroll Menu """ @@ -793,7 +793,7 @@ def _jump_to_bottom(self, viewport_height: int) -> None: self._top_view = 0 - def add_item(self, item: Any): # How to type hint item? - typing.Portocol supports from python3.7 only + def add_item(self, item: Any) -> None: """Adds an item to the menu. Parameters @@ -806,7 +806,7 @@ def add_item(self, item: Any): # How to type hint item? - typing.Portocol suppor self._view_items.append(item) - def add_item_list(self, item_list: List[Any]): # how to type hint item? - typing.Portocol supports from python3.7 only + def add_item_list(self, item_list: List[Any]) -> None: """Adds a list of items to the scroll menu. @@ -833,7 +833,7 @@ def remove_selected_item(self) -> None: self._selected_item = self._selected_item - 1 - def remove_item(self, item) -> None: + def remove_item(self, item: Any) -> None: """Function that removes a specific item from the menu Parameters @@ -910,7 +910,7 @@ def __init__(self, logger, checked_char): self._checked_char = checked_char - def add_item(self, item): + def add_item(self, item: Any) -> None: """Extends base class function, item is added and marked as unchecked to start Parameters diff --git a/py_cui/widget_set.py b/py_cui/widget_set.py index 9b6e1a8..94e6791 100644 --- a/py_cui/widget_set.py +++ b/py_cui/widget_set.py @@ -16,7 +16,6 @@ if TYPE_CHECKING: import py_cui - import py_cui.widgets class WidgetSet: diff --git a/py_cui/widgets.py b/py_cui/widgets.py index b55d084..a2c6ef5 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -23,12 +23,13 @@ import curses import inspect -from typing import Callable, List, Dict, Tuple, Any, Optional import py_cui import py_cui.ui import py_cui.colors import py_cui.errors +from typing import Callable, List, Dict, Tuple, Any, Optional + class Widget(py_cui.ui.UIElement): """Top Level Widget Base Class From dd6f58aff83c35db33d85761db030188ef87ee13 Mon Sep 17 00:00:00 2001 From: mohan-cloud Date: Tue, 13 Jul 2021 16:17:50 +0530 Subject: [PATCH 31/40] Add type annotation for _keybindings and removed unnecessary comments --- py_cui/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index e291c0c..ca3e3f1 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -177,7 +177,7 @@ def __init__(self, num_rows: int, num_cols: int, auto_focus_buttons: bool=True, self._on_draw_update_func: Optional[Callable[[],Any]] = None # Top level keybindings. Exit key is 'q' by default - self._keybindings: Dict[int,Any] = {} # to be discussed + 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 @@ -212,7 +212,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: # Keycodes are type hinted as int + 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 @@ -888,7 +888,7 @@ 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] # using temp variable for mypy + widget = self.get_widgets()[widget_id] if widget is not None: if widget._contains_position(x, y): return widget From b29204fa9219fa016ce40ecdbbe577020757700c Mon Sep 17 00:00:00 2001 From: PabloLec Date: Mon, 27 Sep 2021 14:31:14 +0200 Subject: [PATCH 32/40] Add a color parameter for message popup and remove default title for warning/error popups. --- docs/popups.md | 5 +++-- py_cui/__init__.py | 14 +++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/popups.md b/docs/popups.md index f3b2938..e9951a8 100644 --- a/docs/popups.md +++ b/docs/popups.md @@ -6,13 +6,14 @@ This page contains information regarding all popups supported by `py_cui`. Pleas **Spawn Command** ``` -show_message_popup(title, text) +show_message_popup(title, text, color = WHITE_ON_BLACK) show_warning_popup(title, text) show_error_popup(title, text) ``` **Usage** -Used to show a simple message, with a different color depending on warning level. +`show_message_popup` takes an optional `color` argument which defaults to standard WHITE_ON_BLACK. +`show_warning_popup` and `show_error_popup` are shorthand for respectively yellow and red colors. **Keys** diff --git a/py_cui/__init__.py b/py_cui/__init__.py index ca3e3f1..0de9ad8 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -38,7 +38,6 @@ import py_cui.errors from py_cui.colors import * - # Version number __version__ = '0.1.4' @@ -1160,7 +1159,7 @@ def add_key_command(self, key: int, command: Callable[[],Any]) -> None: # Popup functions. Used to display messages, warnings, and errors to the user. - def show_message_popup(self, title: str, text: str) -> None: + def show_message_popup(self, title: str, text: str, color: int = WHITE_ON_BLACK) -> None: """Shows a message popup Parameters @@ -1169,9 +1168,10 @@ def show_message_popup(self, title: str, text: str) -> None: Message title text : str Message text + color: int + Popup color with format FOREGOUND_ON_BACKGROUND. See colors module. Default: WHITE_ON_BLACK. """ - color = 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}') @@ -1187,9 +1187,7 @@ def show_warning_popup(self, title: str, text: str) -> None: Warning text """ - color = YELLOW_ON_BLACK - self._popup = py_cui.popups.MessagePopup(self, 'WARNING - ' + title, text, color, self._renderer, self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') + self.show_message_popup(title=title, text=text, color=YELLOW_ON_BLACK) def show_error_popup(self, title: str, text: str) -> None: @@ -1203,9 +1201,7 @@ def show_error_popup(self, title: str, text: str) -> None: Error text """ - color = RED_ON_BLACK - self._popup = py_cui.popups.MessagePopup(self, 'ERROR - ' + title, text, color, self._renderer, self._logger) - self._logger.debug(f'Opened {str(type(self._popup))} popup with title {title}') + self.show_message_popup(title=title, text=text, color=RED_ON_BLACK) def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]): From 68ab8f1770896eaacbda6d62a21d9061468af648 Mon Sep 17 00:00:00 2001 From: Pablo Lecolinet Date: Wed, 29 Sep 2021 12:52:27 +0200 Subject: [PATCH 33/40] Set `_stopped` flag to False on start --- py_cui/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index ca3e3f1..6ffa4dd 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -313,6 +313,7 @@ def start(self) -> None: """ self._logger.info(f'Starting {self._title} CUI') + self._stopped = False curses.wrapper(self._draw) From 1d7f943be062ab4d5f16dd5ae7ad51c59c3c6b82 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Thu, 30 Sep 2021 14:45:43 -0400 Subject: [PATCH 34/40] Fixing typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf021eb..9d163d7 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ # py_cui -`py_cui` is a python library meant to simplify writing console user interfaces in python. It relies on `curses` for terminal rendering, which is traditionally a unix-specific, however, you may use the [windows-curses](https://github.com/zephyrproject-rtos/windows-curses) module to run `py_cui` on windows. +`py_cui` is a python library meant to simplify writing console user interfaces in python. It relies on `curses` for terminal rendering, which is traditionally unix-specific, however, you may use the [windows-curses](https://github.com/zephyrproject-rtos/windows-curses) module to run `py_cui` on windows. The main advantage `py_cui` has over typical text-based user interface builders is that it relies on widgets and a grid layout manager like most traditional graphical user interfaces. You may define a grid size, and then drop predefined widgets onto it into specific grid locations. Widgets can also be stretched accross multiple grid rows and columns. If you've ever made a Tkinter GUI, you will feel right at home. In addition, `py_cui` has support for a rich collection of interactive popups such as menus, forms, file dialogs, and more. From 456a93e75d9c7de430b806719ceae2b516d915f4 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sat, 2 Oct 2021 18:09:19 -0400 Subject: [PATCH 35/40] Fixing crash when widget has no neighbors --- py_cui/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index a388079..1784535 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1020,7 +1020,7 @@ def _check_if_neighbor_exists(self, direction: int) -> Optional[int]: 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: + if neighbors is None or len(neighbors) == 0: return None # We select the best match to jump to (first neighbor) From 51e2103258339f693862dd89c77280c6ed352a70 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sat, 2 Oct 2021 18:09:32 -0400 Subject: [PATCH 36/40] Fixing typo in mouse example --- examples/mouse.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/mouse.py b/examples/mouse.py index b086dff..3b4ed57 100644 --- a/examples/mouse.py +++ b/examples/mouse.py @@ -6,7 +6,7 @@ class MouseApp: def __init__(self, root: py_cui.PyCUI): - # Initialize our two widgets, a button and a mous press log + # Initialize our two widgets, a button and a mouse press log self.root = root self.button_presser = self.root.add_button('Press Me!', 0, 0) self.mouse_press_log = self.root.add_text_block('Mouse Presses', 1, 0, row_span=2) @@ -23,21 +23,20 @@ def __init__(self, root: py_cui.PyCUI): self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_RELEASED, lambda: self.mouse_press_log.set_text('Left Released\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.LEFT_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Left Triple\n' + self.mouse_press_log.get())) - + # Demonstration of how to add mouse commands for middle click events self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_CLICK, lambda: self.mouse_press_log.set_text('Middle Single\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_DBL_CLICK, lambda: self.mouse_press_log.set_text('Middle Double\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_PRESSED, lambda: self.mouse_press_log.set_text('Middle Pressed\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_RELEASED, lambda: self.mouse_press_log.set_text('Middle Released\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.MIDDLE_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Middle Triple\n' + self.mouse_press_log.get())) - # Demonstration of how to add mouse commands for remaining right click events + # Demonstration of how to add mouse commands for right click events self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_CLICK, lambda: self.mouse_press_log.set_text('Right Single\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_DBL_CLICK, lambda: self.mouse_press_log.set_text('Right Double\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_PRESSED, lambda: self.mouse_press_log.set_text('Right Pressed\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_RELEASED, lambda: self.mouse_press_log.set_text('Right Released\n' + self.mouse_press_log.get())) self.button_presser.add_mouse_command(KEYS.RIGHT_MOUSE_TRPL_CLICK, lambda: self.mouse_press_log.set_text('Right Triple\n' + self.mouse_press_log.get())) - # Two scroll menus demonstrating how to handle drag and drop functionality with py_cui self.drag_from = self.root.add_scroll_menu('Drag From', 0, 1, row_span=3) self.drag_to = self.root.add_scroll_menu('Drag To', 0, 2, row_span=3) @@ -50,7 +49,6 @@ def __init__(self, root: py_cui.PyCUI): self.current_clipboard_elem = None - def drag(self): self.current_clipboard_elem = self.drag_from.get() @@ -66,7 +64,6 @@ def print_left_press_with_coords(self, x, y): self.mouse_press_log.set_text(f'Left Single w/ coords {x}, {y}\n' + self.mouse_press_log.get()) - root = py_cui.PyCUI(3, 3) MouseApp(root) root.start() \ No newline at end of file From b67e0d4f849e3303c1fd656a17c341db71bbb35c Mon Sep 17 00:00:00 2001 From: jwlodek Date: Sat, 2 Oct 2021 18:37:36 -0400 Subject: [PATCH 37/40] Fixing up on_selection_change for menus, move to parent class, allow for 0 or 1 param functions --- py_cui/__init__.py | 1 - py_cui/ui.py | 50 +++++++++++++++++++++++++++++++++++++++++++++- py_cui/widgets.py | 26 +++--------------------- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/py_cui/__init__.py b/py_cui/__init__.py index 1784535..66c585d 100644 --- a/py_cui/__init__.py +++ b/py_cui/__init__.py @@ -1629,7 +1629,6 @@ def _draw(self, stdscr) -> None: # Initialization and size adjustment stdscr.erase() - # If the user defined an update function to fire on each draw call, # Run it here. This can of course be also handled user-side # through a separate thread. diff --git a/py_cui/ui.py b/py_cui/ui.py index 5833a52..66445a0 100644 --- a/py_cui/ui.py +++ b/py_cui/ui.py @@ -10,7 +10,9 @@ import py_cui import py_cui.errors import py_cui.colors -from typing import Any, List, Optional, Tuple + +import inspect +from typing import Any, List, Optional, Tuple, Callable class UIElement: @@ -680,6 +682,7 @@ def __init__(self, logger): self._selected_item = 0 self._page_scroll_len = 5 self._view_items = [] + self._on_selection_change: Optional[Callable[[Any],Any]] = None def clear(self) -> None: @@ -693,6 +696,51 @@ def clear(self) -> None: self._logger.info('Clearing menu') + def set_on_selection_change_event(self, on_selection_change_event: Callable[[Any],Any]): + """Function that sets the function fired when menu selection changes. + + Event function must take 0 or 1 parameters. If 1 parameter, the new selcted item will be passed in. + + Parameters + ---------- + on_selection_change_event : Callable + Callable function that takes in as an argument the newly selected element + + Raises + ------ + TypeError + Raises a type error if event function is not callable + """ + + #mypy false-positive + if not isinstance(on_selection_change_event, Callable): #type: ignore + raise TypeError('On selection change event must be a Callable!') + + self._on_selection_change = on_selection_change_event + + + 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 + try: + num_args = len(inspect.signature(self._on_selection_change).parameters) + except ValueError: + self._logger.error('Failed to get on_selection_change signature!') + except TypeError: + self._logger.error('Type of object not supported for signature identification!') + + # Depending on the number of parameters for the self._on_selection_change, pass in the x and y + # values, or do nothing + if num_args == 1: + self._on_selection_change(self.get()) + elif num_args == 0: + self._on_selection_change() + else: + raise ValueError('On selection change event must accept either 0 or 1 parameters!') + def get_selected_item_index(self) -> int: """Gets the currently selected item diff --git a/py_cui/widgets.py b/py_cui/widgets.py index a2c6ef5..56b38d4 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -466,30 +466,9 @@ def __init__(self, id, title: str, grid: 'py_cui.grid.Grid', row: int, column: i Widget.__init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger) py_cui.ui.MenuImplementation.__init__(self, logger) - self._on_selection_change: Optional[Callable[[Any],Any]] = None self.set_help_text('Focus mode on ScrollMenu. Use Up/Down/PgUp/PgDown/Home/End to scroll, Esc to exit.') - def set_on_selection_change_event(self, on_selection_change_event: Callable[[Any],Any]): - """Function that sets the function fired when menu selection changes, with the new selection as an arg - - Parameters - ---------- - on_selection_change_event : Callable - Callable function that takes in as an argument the newly selected element - - Raises - ------ - TypeError - Raises a type error if event function is not callable - """ - #mypy false-positive - if not isinstance(on_selection_change_event, Callable): #type: ignore - raise TypeError('On selection change event must be a Callable!') - - self._on_selection_change = on_selection_change_event - - def _handle_mouse_press(self, x: int, y: int, mouse_event: int): """Override of base class function, handles mouse press in menu @@ -509,7 +488,7 @@ def _handle_mouse_press(self, x: int, y: int, mouse_event: int): self.set_selected_item_index(elem_clicked) if self.get_selected_item_index() != current and self._on_selection_change is not None: - self._on_selection_change(self.get()) + self._process_selection_change_event() # For scroll menu, handle custom mouse press after initial event, since we will likely want to # have access to the newly selected item @@ -546,7 +525,8 @@ def _handle_key_press(self, key_pressed: int) -> None: if key_pressed == py_cui.keys.KEY_PAGE_DOWN: self._jump_down(viewport_height) if self.get_selected_item_index() != current and self._on_selection_change is not None: - self._on_selection_change(self.get()) + + self._process_selection_change_event() def _draw(self) -> None: From c68802f01cae0c8287d31cb128f939d3e66ac582 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Fri, 29 Oct 2021 16:18:39 -0400 Subject: [PATCH 38/40] Adding auto-generated docs for 0.1.4 release --- docs/DocstringGenerated/Colors.md | 10 +- docs/DocstringGenerated/Controls/Slider.md | 26 +- docs/DocstringGenerated/Debug.md | 327 ++++++++++++++++-- docs/DocstringGenerated/Dialogs/Filedialog.md | 65 ++-- docs/DocstringGenerated/Dialogs/Form.md | 52 +-- docs/DocstringGenerated/Grid.md | 17 +- docs/DocstringGenerated/Keys.md | 4 +- docs/DocstringGenerated/Popups.md | 82 ++++- docs/DocstringGenerated/PyCui.md | 221 +++++++----- docs/DocstringGenerated/Renderer.md | 34 +- docs/DocstringGenerated/Ui.md | 299 ++++++++++------ docs/DocstringGenerated/WidgetSet.md | 25 +- docs/DocstringGenerated/Widgets.md | 140 +++++--- 13 files changed, 907 insertions(+), 395 deletions(-) diff --git a/docs/DocstringGenerated/Colors.md b/docs/DocstringGenerated/Colors.md index 025efb2..4c3d1c6 100644 --- a/docs/DocstringGenerated/Colors.md +++ b/docs/DocstringGenerated/Colors.md @@ -50,7 +50,7 @@ Class representing a text color rendering rule ### __init__ ```python -def __init__(self, regex, color, selected_color, rule_type, match_type, region, include_whitespace, logger) +def __init__(self, regex: str, color: int, selected_color: int, rule_type: str, match_type: str, region: List[int], include_whitespace: bool, logger) ``` Constructor for ColorRule object @@ -77,7 +77,7 @@ Constructor for ColorRule object ### _check_match ```python -def _check_match(self, line) +def _check_match(self, line: str) -> bool ``` Checks if the color rule matches a line @@ -104,7 +104,7 @@ Checks if the color rule matches a line ### _generate_fragments_regex ```python -def _generate_fragments_regex(self, widget, render_text, selected) +def _generate_fragments_regex(self, widget: Union['py_cui.widgets.Widget','py_cui.ui.UIElement'], render_text:str, selected) -> List[List[Union[int,str]]] ``` Splits text into fragments based on regular expression @@ -132,7 +132,7 @@ Splits text into fragments based on regular expression ### _split_text_on_region ```python -def _split_text_on_region(self, widget, render_text, selected) +def _split_text_on_region(self, widget: Union['py_cui.widgets.Widget','py_cui.ui.UIElement'], render_text: str, selected) -> List[List[Union[str,int]]]: # renderer._generate_text_color_fragments passes a uielement and not a widge ``` Splits text into fragments based on region @@ -160,7 +160,7 @@ Splits text into fragments based on region ### generate_fragments ```python -def generate_fragments(self, widget, line, render_text, selected=False) +def generate_fragments(self, widget: Union['py_cui.widgets.Widget','py_cui.ui.UIElement'], line: str, render_text: str, selected=False) -> Tuple[List[List[Union[str,int]]],bool] ``` Splits text into fragments if matched line to regex diff --git a/docs/DocstringGenerated/Controls/Slider.md b/docs/DocstringGenerated/Controls/Slider.md index 75947f6..10d440b 100644 --- a/docs/DocstringGenerated/Controls/Slider.md +++ b/docs/DocstringGenerated/Controls/Slider.md @@ -40,7 +40,7 @@ class SliderImplementation(py_cui.ui.UIImplementation) ### __init__ ```python -def __init__(self, min_val, max_val, init_val, step, logger) +def __init__(self, min_val: int, max_val: int, init_val: int, step: int, logger) ``` @@ -54,7 +54,7 @@ def __init__(self, min_val, max_val, init_val, step, logger) ### set_bar_char ```python -def set_bar_char(self, char) +def set_bar_char(self, char: str) -> None ``` @@ -104,7 +104,7 @@ Steps up or down the value in offset fashion. ### get_slider_value ```python -def get_slider_value(self) +def get_slider_value(self) -> float ``` @@ -126,7 +126,7 @@ Returns current slider value. ### set_slider_step ```python -def set_slider_step(self, step) +def set_slider_step(self, step: int) -> None ``` @@ -204,7 +204,7 @@ def __init__(self, id, title, grid, row, column, row_span, column_span ### toggle_title ```python -def toggle_title(self) +def toggle_title(self) -> None ``` Toggles visibility of the widget's name. @@ -218,7 +218,7 @@ Toggles visibility of the widget's name. ### toggle_border ```python -def toggle_border(self) +def toggle_border(self) -> None ``` Toggles visibility of the widget's border. @@ -232,7 +232,7 @@ Toggles visibility of the widget's border. ### toggle_value ```python -def toggle_value(self) +def toggle_value(self) -> None ``` Toggles visibility of the widget's current value in integer. @@ -246,7 +246,7 @@ Toggles visibility of the widget's current value in integer. ### align_to_top ```python -def align_to_top(self) +def align_to_top(self) -> None ``` Aligns widget height to top. @@ -260,7 +260,7 @@ Aligns widget height to top. ### align_to_middle ```python -def align_to_middle(self) +def align_to_middle(self) -> None ``` Aligns widget height to middle. default configuration. @@ -274,7 +274,7 @@ Aligns widget height to middle. default configuration. ### align_to_bottom ```python -def align_to_bottom(self) +def align_to_bottom(self) -> None ``` Aligns widget height to bottom. @@ -288,7 +288,7 @@ Aligns widget height to bottom. ### _custom_draw_with_border ```python -def _custom_draw_with_border(self, start_y: int, content: str) +def _custom_draw_with_border(self, start_y: int, content: str) -> None ``` @@ -339,7 +339,7 @@ Internal implementation to generate progression bar. ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base class draw function. @@ -353,7 +353,7 @@ Override of base class draw function. ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` diff --git a/docs/DocstringGenerated/Debug.md b/docs/DocstringGenerated/Debug.md index 66814bc..3d94c21 100644 --- a/docs/DocstringGenerated/Debug.md +++ b/docs/DocstringGenerated/Debug.md @@ -8,6 +8,8 @@ Module containing py_cui logging utilities Class | Doc -----|----- + LiveDebugImplementation(py_cui.ui.MenuImplementation) | Implementation class for the live debug menu - builds off of the scroll menu implementation + LiveDebugElement(py_cui.ui.UIElement, LiveDebugImplementation) | UIElement class for the live debug utility. extends from base UIElement class and LiveDebugImplementation PyCUILogger(logging.Logger) | Custom logger class for py_cui, extends the base logging.Logger Class #### Functions @@ -23,7 +25,7 @@ Module containing py_cui logging utilities ### _enable_logging ```python -def _enable_logging(logger, replace_log_file=True, filename='py_cui_log.txt', logging_level=logging.DEBUG) +def _enable_logging(logger: 'PyCUILogger', replace_log_file: bool=True, filename: str='py_cui.log', logging_level=logging.DEBUG) ``` Function that creates basic logging configuration for selected logger @@ -53,7 +55,7 @@ Function that creates basic logging configuration for selected logger ### _initialize_logger ```python -def _initialize_logger(py_cui_root, name=None, custom_logger=True) +def _initialize_logger(py_cui_root: 'py_cui.PyCUI', name: Optional[str]=None, custom_logger: bool=True) ``` Function that retrieves an instance of either the default or custom py_cui logger. @@ -79,6 +81,215 @@ Function that retrieves an instance of either the default or custom py_cui logge +## LiveDebugImplementation(py_cui.ui.MenuImplementation) + +```python +class LiveDebugImplementation(py_cui.ui.MenuImplementation) +``` + +Implementation class for the live debug menu - builds off of the scroll menu implementation + + + + +#### Attributes + + Attribute | Type | Doc +-----|----------|----- + level | int | Debug level at which to display messages. Can be separate from the default logging level + _buffer_size | List[str] | Number of log messages to keep buffered in the live debug window + +#### Methods + + Method | Doc +-----|----- + print_to_buffer | Override of default MenuImplementation add_item function + + + + +### __init__ + +```python +def __init__(self, parent_logger) +``` + +Initializer for LiveDebugImplementation + + + + + + + +### print_to_buffer + +```python +def print_to_buffer(self, msg: str, log_level) -> None +``` + +Override of default MenuImplementation add_item function + + + +If items override the buffer pop the oldest log message + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + msg | str | Log message to add + + + + + + + + +## LiveDebugElement(py_cui.ui.UIElement, LiveDebugImplementation) + +```python +class LiveDebugElement(py_cui.ui.UIElement, LiveDebugImplementation) +``` + +UIElement class for the live debug utility. extends from base UIElement class and LiveDebugImplementation + + + +#### Methods + + Method | Doc +-----|----- + get_absolute_start_pos | Override of base UI element class function. Sets start position relative to entire UI size + get_absolute_stop_pos | Override of base UI element class function. Sets stop position relative to entire UI size + _handle_mouse_press | Override of base class function, handles mouse press in menu + _handle_key_press | Override of base class function. + _draw | Overrides base class draw function. Mostly a copy of ScrollMenu widget - but reverse item list + + + + +### __init__ + +```python +def __init__(self, parent_logger) +``` + +Initializer for LiveDebugElement class + + + + + + + +### get_absolute_start_pos + +```python +def get_absolute_start_pos(self) -> Tuple[int,int] +``` + +Override of base UI element class function. Sets start position relative to entire UI size + + + + +#### Returns + + Return Variable | Type | Doc +-----|----------|----- + start_x, start_y | int, int | Start position x, y coords in terminal characters + + + + + +### get_absolute_stop_pos + +```python +def get_absolute_stop_pos(self) -> Tuple[int,int] +``` + +Override of base UI element class function. Sets stop position relative to entire UI size + + + + +#### Returns + + Return Variable | Type | Doc +-----|----------|----- + stop_x, stop_y | int, int | Stop position x, y coords in terminal characters + + + + + +### _handle_mouse_press + +```python +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None +``` + +Override of base class function, handles mouse press in menu + + + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + x, y | int | Coordinates of mouse press + mouse_event | int | Key code for py_cui mouse event + + + + + +### _handle_key_press + +```python +def _handle_key_press(self, key_pressed: int) -> None +``` + +Override of base class function. + + + +Essentially the same as the ScrollMenu widget _handle_key_press, with the exception that Esc breaks +out of live debug mode. + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + key_pressed | int | The keycode of the pressed key + + + + + +### _draw + +```python +def _draw(self) -> None +``` + +Overrides base class draw function. Mostly a copy of ScrollMenu widget - but reverse item list + + + + + + + + + + ## PyCUILogger(logging.Logger) ```python @@ -101,13 +312,16 @@ Custom logger class for py_cui, extends the base logging.Logger Class Method | Doc -----|----- - _assign_root_window | Attaches logger to the root window for live debugging + is_live_debug_enabled | + toggle_live_debug | + draw_live_debug | Function that draws the live debug UI element if applicable + _assign_root_window | Function that assigns a PyCUI root object to the logger. Important for live-debug hooks _get_debug_text | Function that generates full debug text for the log - info | Adds stacktrace info to log - debug | Function that allows for live debugging of py_cui programs by displaying log messages in the satus bar - warn | Function that allows for live debugging of py_cui programs by displaying log messages in the satus bar - error | Function that displays error messages live in status bar for py_cui logging - toggle_live_debug | Toggles live debugging mode + info | Override of base logger info function to add hooks for live debug mode + debug | Override of base logger debug function to add hooks for live debug mode + warn | Override of base logger warn function to add hooks for live debug mode + error | Override of base logger error function to add hooks for live debug mode + critical | Override of base logger critical function to add hooks for live debug mode @@ -133,16 +347,65 @@ Initializer for the PyCUILogger helper class +### is_live_debug_enabled + +```python +def is_live_debug_enabled(self) +``` + + + + + + + + + +### toggle_live_debug + +```python +def toggle_live_debug(self) +``` + + + + + + + + + +### draw_live_debug + +```python +def draw_live_debug(self) +``` + +Function that draws the live debug UI element if applicable + + + + + + + ### _assign_root_window ```python -def _assign_root_window(self, py_cui_root) +def _assign_root_window(self, py_cui_root: 'py_cui.PyCUI') -> None ``` -Attaches logger to the root window for live debugging +Function that assigns a PyCUI root object to the logger. Important for live-debug hooks + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + py_cui_root | PyCUI | Root PyCUI object for the application + @@ -150,7 +413,7 @@ Attaches logger to the root window for live debugging ### _get_debug_text ```python -def _get_debug_text(self, text) +def _get_debug_text(self, text: str) -> str ``` Function that generates full debug text for the log @@ -158,16 +421,29 @@ Function that generates full debug text for the log +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + text | str | Log message + +#### Returns + + Return Variable | Type | Doc +-----|----------|----- + msg | str | Log message with function, file, and line num info + + ### info ```python -def info(self, text) +def info(self, msg: Any, *args, **kwargs) -> None : # to overcome signature mismatch in erro ``` -Adds stacktrace info to log +Override of base logger info function to add hooks for live debug mode @@ -185,10 +461,10 @@ Adds stacktrace info to log ### debug ```python -def debug(self, text) +def debug(self, msg: str, *args, **kwargs) -> None ``` -Function that allows for live debugging of py_cui programs by displaying log messages in the satus bar +Override of base logger debug function to add hooks for live debug mode @@ -206,10 +482,10 @@ Function that allows for live debugging of py_cui programs by displaying log mes ### warn ```python -def warn(self, text) +def warn(self, msg: str, *args, **kwargs) -> None ``` -Function that allows for live debugging of py_cui programs by displaying log messages in the satus bar +Override of base logger warn function to add hooks for live debug mode @@ -227,10 +503,10 @@ Function that allows for live debugging of py_cui programs by displaying log mes ### error ```python -def error(self, text) +def error(self, msg: str, *args, **kwargs) -> None ``` -Function that displays error messages live in status bar for py_cui logging +Override of base logger error function to add hooks for live debug mode @@ -245,17 +521,24 @@ Function that displays error messages live in status bar for py_cui logging -### toggle_live_debug +### critical ```python -def toggle_live_debug(self, level=logging.ERROR) +def critical(self, msg: str, *args, **kwargs) -> None ``` -Toggles live debugging mode +Override of base logger critical function to add hooks for live debug mode +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + text | str | The log text ot display + + diff --git a/docs/DocstringGenerated/Dialogs/Filedialog.md b/docs/DocstringGenerated/Dialogs/Filedialog.md index cafbbd5..73c33bf 100644 --- a/docs/DocstringGenerated/Dialogs/Filedialog.md +++ b/docs/DocstringGenerated/Dialogs/Filedialog.md @@ -28,7 +28,7 @@ Implementation, widget, and popup classes for file selection dialogs ### is_filepath_hidden ```python -def is_filepath_hidden(path) +def is_filepath_hidden(path: str) -> bool ``` Function checks if file or folder is considered "hidden" @@ -86,7 +86,7 @@ Simple helper class defining a single file or directory ### __init__ ```python -def __init__(self, elem_type, name, fullpath, ascii_icons=False) +def __init__(self, elem_type: str, name: str, fullpath: str, ascii_icons: bool=False) ``` Intializer for FilDirElem @@ -100,7 +100,7 @@ Intializer for FilDirElem ### get_path ```python -def get_path(self) +def get_path(self) -> str ``` Getter for path @@ -121,7 +121,7 @@ Getter for path ### __str__ ```python -def __str__(self) +def __str__(self) -> str ``` Override of to-string function @@ -174,7 +174,7 @@ Extension of menu implementation that allows for listing files and dirs in a loc ### __init__ ```python -def __init__(self, initial_loc, dialog_type, ascii_icons, logger, limit_extensions = [], show_hidden=False) +def __init__(self, initial_loc: str, dialog_type: str, ascii_icons, logger, limit_extensions: List[str] = [], show_hidden: bool=False) ``` Initalizer for the file select menu implementation. Includes some logic for getting list of file and folders. @@ -188,7 +188,7 @@ Initalizer for the file select menu implementation. Includes some logic for gett ### refresh_view ```python -def refresh_view(self) +def refresh_view(self) -> None ``` Function that refreshes the current list of files and folders in view @@ -237,7 +237,7 @@ Displays list of files and dirs in a given location ### __init__ ```python -def __init__(self, root, initial_dir, dialog_type, ascii_icons, title, color, command, renderer, logger, limit_extensions=[]) +def __init__(self, root, initial_dir, dialog_type: str, ascii_icons, title, color, command, renderer, logger, limit_extensions: List[str]=[]) ``` Initializer for MenuPopup. Uses MenuImplementation as base @@ -251,7 +251,7 @@ Initializer for MenuPopup. Uses MenuImplementation as base ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute start position @@ -272,7 +272,7 @@ Override of base function. Uses the parent element do compute start position ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute stop position @@ -293,7 +293,7 @@ Override of base function. Uses the parent element do compute stop position ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base handle key press function @@ -316,7 +316,7 @@ Enter key runs command, Escape key closes menu ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Overrides base class draw function @@ -377,7 +377,7 @@ Initializer for the FormFieldElement class ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute start position @@ -398,7 +398,7 @@ Override of base function. Uses the parent element do compute start position ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute stop position @@ -419,7 +419,7 @@ Override of base function. Uses the parent element do compute stop position ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Override of base class. Updates text field variables for form field @@ -433,7 +433,7 @@ Override of base class. Updates text field variables for form field ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Handles text input for the field. Called by parent @@ -447,7 +447,7 @@ Handles text input for the field. Called by parent ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Draw function for the field. Called from parent. Essentially the same as a TextboxPopup @@ -510,7 +510,7 @@ Initializer for Button Widget ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute start position @@ -531,7 +531,7 @@ Override of base function. Uses the parent element do compute start position ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute stop position @@ -552,7 +552,7 @@ Override of base function. Uses the parent element do compute stop position ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None ``` Handles mouse presses @@ -566,6 +566,7 @@ Handles mouse presses -----|----------|----- x | int | x coordinate of click in characters y | int | y coordinate of click in characters + mouse_event | int | Mouse event keycode of mouse press @@ -574,7 +575,7 @@ Handles mouse presses ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base class, adds ENTER listener that runs the button's command @@ -595,7 +596,7 @@ Override of base class, adds ENTER listener that runs the button's command ### perform_command ```python -def perform_command(self) +def perform_command(self) -> None ``` @@ -609,7 +610,7 @@ def perform_command(self) ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base class draw function @@ -666,7 +667,7 @@ Initializer for Internal form Popup ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base class, close in parent instead of root @@ -737,7 +738,7 @@ Initalizer for the FileDialogPopup ### _submit ```python -def _submit(self, output) +def _submit(self, output: str) -> None ``` @@ -751,7 +752,7 @@ def _submit(self, output) ### display_warning ```python -def display_warning(self, message) +def display_warning(self, message: str) -> None ``` Helper function for showing internal popup warning message @@ -772,7 +773,7 @@ Helper function for showing internal popup warning message ### output_valid ```python -def output_valid(self, output) +def output_valid(self, output) -> Tuple[bool,Optional[str]] ``` @@ -786,7 +787,7 @@ def output_valid(self, output) ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Override of base class, computes position based on root dimensions @@ -807,7 +808,7 @@ Override of base class, computes position based on root dimensions ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Override of base class, computes position based on root dimensions @@ -828,7 +829,7 @@ Override of base class, computes position based on root dimensions ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Override of base class function @@ -844,7 +845,7 @@ Also updates all form field elements in the form ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed:int) -> None ``` Override of base class. Here, we handle tabs, enters, and escapes @@ -867,7 +868,7 @@ All other key presses are passed to the currently selected field element ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None ``` Override of base class function @@ -890,7 +891,7 @@ Simply enters the appropriate field when mouse is pressed on it ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base class. diff --git a/docs/DocstringGenerated/Dialogs/Form.md b/docs/DocstringGenerated/Dialogs/Form.md index 6e10b8f..7131e86 100644 --- a/docs/DocstringGenerated/Dialogs/Form.md +++ b/docs/DocstringGenerated/Dialogs/Form.md @@ -67,7 +67,7 @@ Class containing basic logic of a field in a form ### __init__ ```python -def __init__(self, fieldname, initial_text, password, required, logger) +def __init__(self, fieldname: str, initial_text: str, password: bool, required: bool, logger) ``` Initializer for base FormFields @@ -81,7 +81,7 @@ Initializer for base FormFields ### get_fieldname ```python -def get_fieldname(self) +def get_fieldname(self) -> str ``` Getter for field name @@ -102,7 +102,7 @@ Getter for field name ### is_valid ```python -def is_valid(self) +def is_valid(self) -> Tuple[bool,Optional[str]] ``` Function that checks if field is valid. @@ -127,7 +127,7 @@ field types (ex. emails etc.) ### is_required ```python -def is_required(self) +def is_required(self) -> bool ``` Checks if field is required @@ -182,7 +182,7 @@ Extension of UI element representing an individual field in the form ### __init__ ```python -def __init__(self, parent_form, field_index, field, init_text, passwd, required, renderer, logger) +def __init__(self, parent_form, field_index: int, field, init_text: str, passwd: bool, required: bool, renderer: 'py_cui.renderer.Renderer', logger) ``` Initializer for the FormFieldElement class @@ -196,7 +196,7 @@ Initializer for the FormFieldElement class ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute start position @@ -217,7 +217,7 @@ Override of base function. Uses the parent element do compute start position ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Override of base function. Uses the parent element do compute stop position @@ -238,7 +238,7 @@ Override of base function. Uses the parent element do compute stop position ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Override of base class. Updates text field variables for form field @@ -252,7 +252,7 @@ Override of base class. Updates text field variables for form field ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Handles text input for the field. Called by parent @@ -266,7 +266,7 @@ Handles text input for the field. Called by parent ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Draw function for the field. Called from parent. Essentially the same as a TextboxPopup @@ -318,7 +318,7 @@ Function fired when submit is called ### __init__ ```python -def __init__(self, field_implementations, required_fields, logger) +def __init__(self, field_implementations: List['FormField'], required_fields: List[str], logger) ``` Initializer for the FormImplemnentation class @@ -332,7 +332,7 @@ Initializer for the FormImplemnentation class ### get_selected_form_index ```python -def get_selected_form_index(self) +def get_selected_form_index(self) -> int ``` Getter for selected form index @@ -353,7 +353,7 @@ Getter for selected form index ### set_selected_form_index ```python -def set_selected_form_index(self, form_index) +def set_selected_form_index(self, form_index: int) -> None ``` Setter for selected form index @@ -374,7 +374,7 @@ Setter for selected form index ### set_on_submit_action ```python -def set_on_submit_action(self, on_submit_action) +def set_on_submit_action(self, on_submit_action: Callable[[],Any]) ``` Setter for callback on submit @@ -395,7 +395,7 @@ Setter for callback on submit ### jump_to_next_field ```python -def jump_to_next_field(self) +def jump_to_next_field(self) -> None ``` Function used to jump between form fields @@ -409,7 +409,7 @@ Function used to jump between form fields ### is_submission_valid ```python -def is_submission_valid(self) +def is_submission_valid(self) -> Tuple[bool,Optional[str]] ``` Function that checks if all fields are filled out correctly @@ -431,7 +431,7 @@ Function that checks if all fields are filled out correctly ### get ```python -def get(self) +def get(self) -> Dict[str,str] ``` Gets values entered into field as a dictionary @@ -497,7 +497,7 @@ A helper class for abstracting a message popup tied to a parent popup ### __init__ ```python -def __init__(self, parent, *args) +def __init__(self, parent: 'FormPopup', *args) ``` Initializer for Internal form Popup @@ -511,7 +511,7 @@ Initializer for Internal form Popup ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base class, close in parent instead of root @@ -576,7 +576,7 @@ def __init__(self, root, fields, passwd_fields, required_fields, fields_init_tex ### get_num_fields ```python -def get_num_fields(self) +def get_num_fields(self) -> int ``` Getter for number of fields @@ -597,7 +597,7 @@ Getter for number of fields ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Override of base class, computes position based on root dimensions @@ -618,7 +618,7 @@ Override of base class, computes position based on root dimensions ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Override of base class, computes position based on root dimensions @@ -639,7 +639,7 @@ Override of base class, computes position based on root dimensions ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Override of base class function @@ -655,7 +655,7 @@ Also updates all form field elements in the form ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base class. Here, we handle tabs, enters, and escapes @@ -678,7 +678,7 @@ All other key presses are passed to the currently selected field element ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None ``` Override of base class function @@ -701,7 +701,7 @@ Simply enters the appropriate field when mouse is pressed on it ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base class. diff --git a/docs/DocstringGenerated/Grid.md b/docs/DocstringGenerated/Grid.md index d9fe2c5..252ca26 100644 --- a/docs/DocstringGenerated/Grid.md +++ b/docs/DocstringGenerated/Grid.md @@ -34,6 +34,7 @@ Class representing the CUI grid _height, _width | int | The height, width in characters of the terminal window _offset_y, _offset_x | int | The number of additional characters found by height mod rows and width mod columns _row_height, _column_width | int | The number of characters in a single grid row, column + _title_bar_offset | int | Title bar row offset. Defaults to 1. Set to 0 if title bar is hidden. _logger | py_cui.debug.PyCUILogger | logger object for maintaining debug messages #### Methods @@ -54,7 +55,7 @@ Class representing the CUI grid ### __init__ ```python -def __init__(self, num_rows, num_columns, height, width, logger) +def __init__(self, num_rows: int, num_columns: int, height: int, width: int, logger: 'py_cui.debug.PyCUILogger') ``` Constructor for the Grid class @@ -78,7 +79,7 @@ Constructor for the Grid class ### get_dimensions ```python -def get_dimensions(self) +def get_dimensions(self) -> Tuple[int,int] ``` Gets dimensions in rows/columns @@ -100,7 +101,7 @@ Gets dimensions in rows/columns ### get_dimensions_absolute ```python -def get_dimensions_absolute(self) +def get_dimensions_absolute(self) -> Tuple[int,int] ``` Gets dimensions of grid in terminal characters @@ -122,7 +123,7 @@ Gets dimensions of grid in terminal characters ### get_offsets ```python -def get_offsets(self) +def get_offsets(self) -> Tuple[int,int] ``` Gets leftover characters for x and y @@ -144,7 +145,7 @@ Gets leftover characters for x and y ### get_cell_dimensions ```python -def get_cell_dimensions(self) +def get_cell_dimensions(self) -> Tuple[int,int] ``` Gets size in characters of single (row, column) cell location @@ -166,7 +167,7 @@ Gets size in characters of single (row, column) cell location ### set_num_rows ```python -def set_num_rows(self, num_rows) +def set_num_rows(self, num_rows: int) -> None ``` Sets the grid row size @@ -193,7 +194,7 @@ Sets the grid row size ### set_num_cols ```python -def set_num_cols(self, num_columns) +def set_num_cols(self, num_columns: int) -> None ``` Sets the grid column size @@ -220,7 +221,7 @@ Sets the grid column size ### update_grid_height_width ```python -def update_grid_height_width(self, height, width) +def update_grid_height_width(self, height: int, width: int) ``` Update grid height and width. Allows for on-the-fly size editing diff --git a/docs/DocstringGenerated/Keys.md b/docs/DocstringGenerated/Keys.md index c48637c..5085a2f 100644 --- a/docs/DocstringGenerated/Keys.md +++ b/docs/DocstringGenerated/Keys.md @@ -17,7 +17,7 @@ Module containing constants and helper functions for dealing with keys. ### get_ascii_from_char ```python -def get_ascii_from_char(char) +def get_ascii_from_char(char: str) -> int ``` Function that converts ascii code to character @@ -44,7 +44,7 @@ Function that converts ascii code to character ### get_char_from_ascii ```python -def get_char_from_ascii(key_num) +def get_char_from_ascii(key_num: int) -> Optional[str] ``` Function that converts a character to an ascii code diff --git a/docs/DocstringGenerated/Popups.md b/docs/DocstringGenerated/Popups.md index 63bacf2..93ccb9e 100644 --- a/docs/DocstringGenerated/Popups.md +++ b/docs/DocstringGenerated/Popups.md @@ -60,7 +60,7 @@ frame ### __init__ ```python -def __init__(self, root, title, text, color, renderer, logger) +def __init__(self, root: 'py_cui.PyCUI', title: str, text: str, color: int, renderer: 'py_cui.renderer.Renderer', logger) ``` Initializer for main popup class. Calls UIElement intialier, and sets some initial values @@ -88,7 +88,7 @@ Function that increments an internal counter ### set_text ```python -def set_text(self, text) +def set_text(self, text: str) -> None ``` Sets popup text (message) @@ -109,7 +109,7 @@ Sets popup text (message) ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Override of base class, computes position based on root dimensions @@ -130,7 +130,7 @@ Override of base class, computes position based on root dimensions ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Override of base class, computes position based on root dimensions @@ -151,7 +151,7 @@ Override of base class, computes position based on root dimensions ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Handles key presses when popup is open @@ -174,7 +174,7 @@ By default, only closes popup when Escape is pressed ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Function that uses renderer to draw the popup @@ -226,7 +226,7 @@ Initializer for MessagePopup ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Draw function for MessagePopup. Calls superclass draw() @@ -284,7 +284,7 @@ Initializer for YesNoPopup ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) ``` Handle key press overwrite from superclass @@ -341,6 +341,7 @@ Class representing a textbox popup Method | Doc -----|----- update_height_width | Need to update all cursor positions on resize + _handle_mouse_press | Override of base class function, handles mouse press in menu _handle_key_press | Override of base handle key press function _draw | Override of base draw function @@ -364,7 +365,7 @@ Initializer for textbox popup. Uses TextBoxImplementation as base ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Need to update all cursor positions on resize @@ -375,10 +376,31 @@ Need to update all cursor positions on resize +### _handle_mouse_press + +```python +def _handle_mouse_press(self, x: int, y: int, mouse_event) -> None +``` + +Override of base class function, handles mouse press in menu + + + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + x, y | int | Coordinates of mouse press + + + + + ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) ``` Override of base handle key press function @@ -399,7 +421,7 @@ Override of base handle key press function ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base draw function @@ -437,6 +459,7 @@ Allows for popup with several menu items to select from Method | Doc -----|----- + _handle_mouse_press | Override of base class function, handles mouse press in menu _handle_key_press | Override of base handle key press function _draw | Overrides base class draw function @@ -446,7 +469,7 @@ Allows for popup with several menu items to select from ### __init__ ```python -def __init__(self, root, items, title, color, command, renderer, logger, run_command_if_none) +def __init__(self, root: 'py_cui.PyCUI', items, title, color, command, renderer, logger, run_command_if_none) ``` Initializer for MenuPopup. Uses MenuImplementation as base @@ -457,10 +480,31 @@ Initializer for MenuPopup. Uses MenuImplementation as base +### _handle_mouse_press + +```python +def _handle_mouse_press(self, x: int, y: int, mouse_event) +``` + +Override of base class function, handles mouse press in menu + + + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + x, y | int | Coordinates of mouse press + + + + + ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base handle key press function @@ -483,7 +527,7 @@ Enter key runs command, Escape key closes menu ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Overrides base class draw function @@ -545,7 +589,7 @@ Initializer for LoadingIconPopup ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) ``` Override of base class function. @@ -568,7 +612,7 @@ Loading icon popups cannot be cancelled, so we wish to avoid default behavior ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Overrides base draw function @@ -630,7 +674,7 @@ Initializer for LoadingBarPopup ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) ``` Override of base class function. @@ -653,7 +697,7 @@ Loading icon popups cannot be cancelled, so we wish to avoid default behavior ### _increment_counter ```python -def _increment_counter(self) +def _increment_counter(self) -> None ``` Function that increments an internal counter @@ -667,7 +711,7 @@ Function that increments an internal counter ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base draw function diff --git a/docs/DocstringGenerated/PyCui.md b/docs/DocstringGenerated/PyCui.md index 098bb80..f59d912 100644 --- a/docs/DocstringGenerated/PyCui.md +++ b/docs/DocstringGenerated/PyCui.md @@ -22,7 +22,7 @@ A python library for intuitively creating CUI/TUI interfaces with pre-built widg ### fit_text ```python -def fit_text(width, text, center=False) +def fit_text(width: int, text: str, center: bool = False) -> str ``` Fits text to screen size @@ -86,6 +86,7 @@ first create an instance of this class, and then add cells + widgets to it. set_refresh_timeout | Sets the CUI auto-refresh timeout to a number of seconds. set_on_draw_update_func | Adds a function that is fired during each draw call of the CUI set_widget_cycle_key | Assigns a key for automatically cycling through widgets in both focus and overview modes + set_toggle_live_debug_key | enable_logging | Function enables logging for py_cui library apply_widget_set | Function that replaces all widgets in a py_cui with those of a different widget set create_new_widget_set | Function that is used to create additional widget sets @@ -107,6 +108,7 @@ first create an instance of this class, and then add cells + widgets to it. add_block_label | Function that adds a new block label to the CUI grid add_button | Function that adds a new button to the CUI grid add_slider | Function that adds a new label to the CUI grid + forget_widget | Function that is used to destroy or "forget" widgets. Forgotten widgets will no longer be drawn get_element_at_position | Returns containing widget for character position _get_horizontal_neighbors | Gets all horizontal (left, right) neighbor widgets _get_vertical_neighbors | Gets all vertical (up, down) neighbor widgets @@ -130,8 +132,7 @@ 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 - get_absolute_size | Returns dimensions of CUI + _refresh_height_width | Function that updates the height and width of the CUI based on terminal window size.""" _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 @@ -145,10 +146,10 @@ first create an instance of this class, and then add cells + widgets to it. ### __init__ ```python -def __init__(self, num_rows, num_cols, auto_focus_buttons=True +def __init__(self, num_rows: int, num_cols: int, auto_focus_buttons: bool=True ``` -Constructor for PyCUI class +Initializer for PyCUI class @@ -159,7 +160,7 @@ Constructor for PyCUI class ### set_refresh_timeout ```python -def set_refresh_timeout(self, timeout) +def set_refresh_timeout(self, timeout: int) ``` Sets the CUI auto-refresh timeout to a number of seconds. @@ -180,7 +181,7 @@ Sets the CUI auto-refresh timeout to a number of seconds. ### set_on_draw_update_func ```python -def set_on_draw_update_func(self, update_function) +def set_on_draw_update_func(self, update_function: Callable[[],Any]) ``` Adds a function that is fired during each draw call of the CUI @@ -201,7 +202,7 @@ Adds a function that is fired during each draw call of the CUI ### set_widget_cycle_key ```python -def set_widget_cycle_key(self, forward_cycle_key=None, reverse_cycle_key=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 @@ -219,10 +220,24 @@ Assigns a key for automatically cycling through widgets in both focus and overvi +### set_toggle_live_debug_key + +```python +def set_toggle_live_debug_key(self, toggle_debug_key: int) -> None +``` + + + + + + + + + ### enable_logging ```python -def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging.DEBUG) +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 @@ -244,7 +259,7 @@ Function enables logging for py_cui library ### apply_widget_set ```python -def apply_widget_set(self, new_widget_set) +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 @@ -271,7 +286,7 @@ Function that replaces all widgets in a py_cui with those of a different widget ### create_new_widget_set ```python -def create_new_widget_set(self, num_rows, num_cols) +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 @@ -302,7 +317,7 @@ for logging support. ### start ```python -def start(self) +def start(self) -> None ``` Function that starts the CUI @@ -316,7 +331,7 @@ Function that starts the CUI ### stop ```python -def stop(self) +def stop(self) -> None ``` Function that stops the CUI, and fires the callback function. @@ -332,7 +347,7 @@ Callback must be a no arg method ### run_on_exit ```python -def run_on_exit(self, command) +def run_on_exit(self, command: Callable[[],Any]) ``` Sets callback function on CUI exit. Must be a no-argument function or lambda function @@ -353,7 +368,7 @@ Sets callback function on CUI exit. Must be a no-argument function or lambda fun ### set_title ```python -def set_title(self, title) +def set_title(self, title: str) -> None ``` Sets the title bar text @@ -374,7 +389,7 @@ Sets the title bar text ### set_status_bar_text ```python -def set_status_bar_text(self, text) +def set_status_bar_text(self, text: str) -> None ``` Sets the status bar text when in overview mode @@ -395,7 +410,7 @@ Sets the status bar text when in overview mode ### _initialize_colors ```python -def _initialize_colors(self) +def _initialize_colors(self) -> None ``` Function for initialzing curses colors. Called when CUI is first created. @@ -409,7 +424,7 @@ Function for initialzing curses colors. Called when CUI is first created. ### _initialize_widget_renderer ```python -def _initialize_widget_renderer(self) +def _initialize_widget_renderer(self) -> None ``` Function that creates the renderer object that will draw each widget @@ -423,7 +438,7 @@ Function that creates the renderer object that will draw each widget ### toggle_unicode_borders ```python -def toggle_unicode_borders(self) +def toggle_unicode_borders(self) -> None ``` Function for toggling unicode based border rendering @@ -437,7 +452,7 @@ Function for toggling unicode based border rendering ### set_widget_border_characters ```python -def set_widget_border_characters(self, upper_left_corner, upper_right_corner, lower_left_corner, lower_right_corner, horizontal, vertical) +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. @@ -463,7 +478,7 @@ Function that can be used to set arbitrary border characters for drawing widget ### get_widgets ```python -def get_widgets(self) +def get_widgets(self) -> Dict[int,Optional['py_cui.widgets.Widget']] ``` Function that gets current set of widgets @@ -475,7 +490,7 @@ Function that gets current set of widgets Return Variable | Type | Doc -----|----------|----- - widgets | dict of str -> widget | dictionary mapping widget IDs to object instances + widgets | dict of int -> widget | dictionary mapping widget IDs to object instances @@ -484,7 +499,7 @@ Function that gets current set of widgets ### add_scroll_menu ```python -def add_scroll_menu(self, title, row, column, row_span=1, column_span=1, padx=1, pady=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 @@ -517,7 +532,7 @@ Function that adds a new scroll menu to the CUI grid ### add_checkbox_menu ```python -def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx=1, pady=0, checked_char='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 @@ -551,7 +566,7 @@ Function that adds a new checkbox menu to the CUI grid ### add_text_box ```python -def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = '', password = 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 @@ -586,7 +601,7 @@ Function that adds a new text box to the CUI grid ### add_text_block ```python -def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = '') -> 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 @@ -620,7 +635,7 @@ Function that adds a new text block to the CUI grid ### add_label ```python -def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 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 @@ -653,7 +668,7 @@ Function that adds a new label to the CUI grid ### add_block_label ```python -def add_block_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, center=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 @@ -687,7 +702,7 @@ Function that adds a new block label to the CUI grid ### add_button ```python -def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, command=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 @@ -721,7 +736,7 @@ Function that adds a new button to the CUI grid ### add_slider ```python -def add_slider(self, title, row, column, row_span=1 +def add_slider(self, title: str, row: int, column: int, row_span: int=1 ``` Function that adds a new label to the CUI grid @@ -755,10 +770,38 @@ Function that adds a new label to the CUI grid +### forget_widget + +```python +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 + + Parameter | Type | Doc +-----|----------|----- + widget | py_cui.widgets.Widget | Widget to remove from the UI + +#### Raises + + Error | Type | Doc +-----|----------|----- + Unknown | TypeError | If input parameter is not of the py_cui widget type + Unknown | KeyError | If input widget does not exist in the current UI or has already been removed. + + + + + ### get_element_at_position ```python -def get_element_at_position(self, x, y) +def get_element_at_position(self, x: int, y: int) -> Optional['py_cui.ui.UIElement'] ``` Returns containing widget for character position @@ -786,7 +829,7 @@ Returns containing widget for character position ### _get_horizontal_neighbors ```python -def _get_horizontal_neighbors(self, widget, direction) +def _get_horizontal_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]] ``` Gets all horizontal (left, right) neighbor widgets @@ -814,7 +857,7 @@ Gets all horizontal (left, right) neighbor widgets ### _get_vertical_neighbors ```python -def _get_vertical_neighbors(self, widget, direction) +def _get_vertical_neighbors(self, widget: 'py_cui.widgets.Widget', direction: int) -> Optional[List[int]] ``` Gets all vertical (up, down) neighbor widgets @@ -842,7 +885,7 @@ Gets all vertical (up, down) neighbor widgets ### _check_if_neighbor_exists ```python -def _check_if_neighbor_exists(self, direction) +def _check_if_neighbor_exists(self, direction: int) -> Optional[int] ``` Function that checks if widget has neighbor in specified cell. @@ -862,7 +905,7 @@ Used for navigating CUI, as arrow keys find the immediate neighbor Return Variable | Type | Doc -----|----------|----- - widget_id | str | The widget neighbor ID if found, None otherwise + widget_id | int | The widget neighbor ID if found, None otherwise @@ -871,7 +914,7 @@ Used for navigating CUI, as arrow keys find the immediate neighbor ### get_selected_widget ```python -def get_selected_widget(self) +def get_selected_widget(self) -> Optional['py_cui.widgets.Widget'] ``` Function that gets currently selected widget @@ -892,7 +935,7 @@ Function that gets currently selected widget ### set_selected_widget ```python -def set_selected_widget(self, widget_id) +def set_selected_widget(self, widget_id: int) -> None ``` Function that sets the selected widget for the CUI @@ -904,7 +947,7 @@ Function that sets the selected widget for the CUI Parameter | Type | Doc -----|----------|----- - widget_id | str | the id of the widget to select + widget_id | int | the id of the widget to select @@ -913,7 +956,7 @@ Function that sets the selected widget for the CUI ### lose_focus ```python -def lose_focus(self) +def lose_focus(self) -> None ``` Function that forces py_cui out of focus mode. @@ -929,7 +972,7 @@ After popup is called, focus is lost ### move_focus ```python -def move_focus(self, widget, auto_press_buttons=True) +def move_focus(self, widget: 'py_cui.widgets.Widget', auto_press_buttons: bool=True) -> None ``` Moves focus mode to different widget @@ -950,7 +993,7 @@ Moves focus mode to different widget ### _cycle_widgets ```python -def _cycle_widgets(self, reverse=False) +def _cycle_widgets(self, reverse: bool=False) -> None ``` Function that is fired if cycle key is pressed to move to next widget @@ -971,7 +1014,7 @@ Function that is fired if cycle key is pressed to move to next widget ### add_key_command ```python -def add_key_command(self, key, command) +def add_key_command(self, key: int, command: Callable[[],Any]) -> None ``` Function that adds a keybinding to the CUI when in overview mode @@ -993,7 +1036,7 @@ Function that adds a keybinding to the CUI when in overview mode ### show_message_popup ```python -def show_message_popup(self, title, text) +def show_message_popup(self, title: str, text: str, color: int = WHITE_ON_BLACK) -> None ``` Shows a message popup @@ -1007,6 +1050,7 @@ Shows a message popup -----|----------|----- title | str | Message title text | str | Message text + color | int | Popup color with format FOREGOUND_ON_BACKGROUND. See colors module. Default: WHITE_ON_BLACK. @@ -1015,7 +1059,7 @@ Shows a message popup ### show_warning_popup ```python -def show_warning_popup(self, title, text) +def show_warning_popup(self, title: str, text: str) -> None ``` Shows a warning popup @@ -1037,7 +1081,7 @@ Shows a warning popup ### show_error_popup ```python -def show_error_popup(self, title, text) +def show_error_popup(self, title: str, text: str) -> None ``` Shows an error popup @@ -1059,7 +1103,7 @@ Shows an error popup ### show_yes_no_popup ```python -def show_yes_no_popup(self, title, command) +def show_yes_no_popup(self, title: str, command: Callable[[bool], Any]) ``` Shows a yes/no popup. @@ -1083,7 +1127,7 @@ The 'command' parameter must be a function with a single boolean parameter ### show_text_box_popup ```python -def show_text_box_popup(self, title, command, password=False) +def show_text_box_popup(self, title: str, command: Callable[[str], Any], password: bool=False) ``` Shows a textbox popup. @@ -1108,7 +1152,7 @@ The 'command' parameter must be a function with a single string parameter ### show_menu_popup ```python -def show_menu_popup(self, title, menu_items, command, run_command_if_none=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. @@ -1134,7 +1178,7 @@ The 'command' parameter must be a function with a single string parameter ### show_loading_icon_popup ```python -def show_loading_icon_popup(self, title, message, callback=None) +def show_loading_icon_popup(self, title: str, message: str, callback: Callable[[],Any]=None) ``` Shows a loading icon popup @@ -1157,7 +1201,7 @@ Shows a loading icon popup ### show_loading_bar_popup ```python -def show_loading_bar_popup(self, title, num_items, callback=None) +def show_loading_bar_popup(self, title: str, num_items: List[int], callback: Callable[[],Any]=None) -> None ``` Shows loading bar popup. @@ -1182,7 +1226,7 @@ Use 'increment_loading_bar' to show progress ### show_form_popup ```python -def show_form_popup(self, title, fields, passwd_fields=[], required=[], callback=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. @@ -1209,7 +1253,7 @@ Used for inputting several fields worth of values ### show_filedialog_popup ```python -def show_filedialog_popup(self, popup_type='openfile', initial_dir='.', callback=None, ascii_icons=True, limit_extensions=[]) +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. @@ -1223,11 +1267,11 @@ Used for inputting several fields worth of values Parameter | Type | Doc -----|----------|----- - title | str | Message title - fields | List[str] | Names of each individual field - passwd_fields | List[str] | Field names that should have characters hidden - required | List[str] | Fields that are required before submission - callback=None | Function | If not none, fired after loading is completed. Must be a no-arg function + popup_type | str | Type of filedialog popup - either openfile, opendir, or saveas + initial_dir | os.PathLike | Path to directory in which to open the file dialog, default "." + callback=None | Callable | If not none, fired after loading is completed. Must be a no-arg function, default=None + ascii_icons | bool | Compatibility option - use ascii icons instead of unicode file/folder icons, default True + limit_extensions | List[str] | Only show files with extensions in this list if not empty. Default, [] @@ -1236,7 +1280,7 @@ Used for inputting several fields worth of values ### increment_loading_bar ```python -def increment_loading_bar(self) +def increment_loading_bar(self) -> None ``` Increments progress bar if loading bar popup is open @@ -1250,7 +1294,7 @@ Increments progress bar if loading bar popup is open ### stop_loading_popup ```python -def stop_loading_popup(self) +def stop_loading_popup(self) -> None ``` Leaves loading state, and closes popup. @@ -1266,7 +1310,7 @@ Must be called by user to escape loading. ### close_popup ```python -def close_popup(self) +def close_popup(self) -> None ``` Closes the popup, and resets focus @@ -1280,34 +1324,43 @@ Closes the popup, and resets focus ### _refresh_height_width ```python -def _refresh_height_width(self, height, width) +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.""" -#### Parameters - - Parameter | Type | Doc ------|----------|----- - height | int | Window height in terminal characters - width | int | Window width in terminal characters - +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 +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 = 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._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 +if widget is not None: +widget.update_height_width() +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) -``` - -Returns dimensions of CUI - - +def get_absolute_size(self) -> Tuple[int,int]: +"""Returns dimensions of CUI #### Returns @@ -1323,7 +1376,7 @@ Returns dimensions of CUI ### _draw_widgets ```python -def _draw_widgets(self) +def _draw_widgets(self) -> None ``` Function that draws all of the widgets to the screen @@ -1337,7 +1390,7 @@ Function that draws all of the widgets to the screen ### _draw_status_bars ```python -def _draw_status_bars(self, stdscr, height, width) +def _draw_status_bars(self, stdscr, height: int, width: int) -> None ``` Draws status bar and title bar @@ -1360,7 +1413,7 @@ Draws status bar and title bar ### _display_window_warning ```python -def _display_window_warning(self, stdscr, error_info) +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 @@ -1382,7 +1435,7 @@ Function that prints some basic error info if there is an error with the CUI ### _handle_key_presses ```python -def _handle_key_presses(self, key_pressed) +def _handle_key_presses(self, key_pressed: int) -> None ``` Function that handles all main loop key presses. @@ -1403,7 +1456,7 @@ Function that handles all main loop key presses. ### _draw ```python -def _draw(self, stdscr) +def _draw(self, stdscr) -> None ``` Main CUI draw loop called by start() diff --git a/docs/DocstringGenerated/Renderer.md b/docs/DocstringGenerated/Renderer.md index 3618c9a..59e5728 100644 --- a/docs/DocstringGenerated/Renderer.md +++ b/docs/DocstringGenerated/Renderer.md @@ -61,7 +61,7 @@ and text required for the cui. All of the functions supplied by the renderer cla ### __init__ ```python -def __init__(self, root, stdscr, logger) +def __init__(self, root: 'py_cui.PyCUI', stdscr, logger) ``` Constructor for renderer object @@ -75,7 +75,7 @@ Constructor for renderer object ### _set_border_renderer_chars ```python -def _set_border_renderer_chars(self, border_char_set) +def _set_border_renderer_chars(self, border_char_set: Dict[str,str]) -> None ``` Function that sets the border characters for ui_elements @@ -96,7 +96,7 @@ Function that sets the border characters for ui_elements ### _set_bold ```python -def _set_bold(self) +def _set_bold(self) -> None ``` Sets bold draw mode @@ -110,7 +110,7 @@ Sets bold draw mode ### _unset_bold ```python -def _unset_bold(self) +def _unset_bold(self) -> None ``` Unsets bold draw mode @@ -124,7 +124,7 @@ Unsets bold draw mode ### set_color_rules ```python -def set_color_rules(self, color_rules) +def set_color_rules(self, color_rules) -> None ``` Sets current color rules @@ -145,7 +145,7 @@ Sets current color rules ### set_color_mode ```python -def set_color_mode(self, color_mode) +def set_color_mode(self, color_mode: int) -> None ``` Sets the output color mode @@ -166,7 +166,7 @@ Sets the output color mode ### unset_color_mode ```python -def unset_color_mode(self, color_mode) +def unset_color_mode(self, color_mode: int) -> None ``` Unsets the output color mode @@ -187,7 +187,7 @@ Unsets the output color mode ### reset_cursor ```python -def reset_cursor(self, ui_element, fill=True) +def reset_cursor(self, ui_element: 'py_cui.ui.UIElement', fill: bool=True) -> None ``` Positions the cursor at the bottom right of the selected element @@ -209,7 +209,7 @@ Positions the cursor at the bottom right of the selected element ### draw_cursor ```python -def draw_cursor(self, cursor_y, cursor_x) +def draw_cursor(self, cursor_y: int, cursor_x: int) -> None ``` Draws the cursor at a particular location @@ -230,7 +230,7 @@ Draws the cursor at a particular location ### draw_border ```python -def draw_border(self, ui_element, fill=True, with_title=True) +def draw_border(self, ui_element: 'py_cui.ui.UIElement', fill: bool=True, with_title: bool=True) -> None ``` Draws ascii border around ui element @@ -253,7 +253,7 @@ Draws ascii border around ui element ### _draw_border_top ```python -def _draw_border_top(self, ui_element, y, with_title) +def _draw_border_top(self, ui_element:'py_cui.ui.UIElement', y: int, with_title: bool) -> None ``` Internal function for drawing top of border @@ -276,7 +276,7 @@ Internal function for drawing top of border ### _draw_border_bottom ```python -def _draw_border_bottom(self, ui_element, y) +def _draw_border_bottom(self, ui_element: 'py_cui.ui.UIElement', y: int) -> None ``` Internal function for drawing bottom of border @@ -298,7 +298,7 @@ Internal function for drawing bottom of border ### _draw_blank_row ```python -def _draw_blank_row(self, ui_element, y) +def _draw_blank_row(self, ui_element: 'py_cui.ui.UIElement', y: int) -> None ``` Internal function for drawing a blank row @@ -320,7 +320,7 @@ Internal function for drawing a blank row ### _get_render_text ```python -def _get_render_text(self, ui_element, line, centered, bordered, selected, start_pos) +def _get_render_text(self, ui_element: 'py_cui.ui.UIElement', line: str, centered: bool, bordered: bool, selected, start_pos:int) -> List[List[Union[int,str]]] ``` Internal function that computes the scope of the text that should be drawn @@ -342,7 +342,7 @@ Internal function that computes the scope of the text that should be drawn Return Variable | Type | Doc -----|----------|----- - render_text | str | The text shortened to fit within given space + render_text | str # to be checked, returns a List of [int,str] | The text shortened to fit within given space @@ -351,7 +351,7 @@ Internal function that computes the scope of the text that should be drawn ### _generate_text_color_fragments ```python -def _generate_text_color_fragments(self, ui_element, line, render_text, selected) +def _generate_text_color_fragments(self, ui_element: 'py_cui.ui.UIElement', line: str, render_text: str, selected) -> List[List[Union[int,str]]] ``` Function that applies color rules to text, dividing them if match is found @@ -380,7 +380,7 @@ Function that applies color rules to text, dividing them if match is found ### draw_text ```python -def draw_text(self, ui_element, line, y, centered = False, bordered = True, selected = False, start_pos = 0) +def draw_text(self, ui_element: 'py_cui.ui.UIElement', line: str, y: int, centered: bool = False, bordered: bool = True, selected: bool = False, start_pos: int = 0) ``` Function that draws ui_element text. diff --git a/docs/DocstringGenerated/Ui.md b/docs/DocstringGenerated/Ui.md index 59620ca..0cc03ba 100644 --- a/docs/DocstringGenerated/Ui.md +++ b/docs/DocstringGenerated/Ui.md @@ -84,7 +84,6 @@ class contains all links to the CUI engine. set_help_text | Sets status bar help text set_focus_text | Sets status bar focus text. Legacy function, overridden by set_focus_text _handle_key_press | Must be implemented by subclass. Used to handle keypresses - add_mouse_press_handler | Sets a mouse press handler function _handle_mouse_press | Can be implemented by subclass. Used to handle mouse presses _draw | Must be implemented by subclasses. Uses renderer to draw element to terminal _assign_renderer | Function that assigns a renderer object to the element @@ -110,7 +109,7 @@ Initializer for UIElement base class ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Must be implemented by subclass, computes the absolute coords of upper-left corner @@ -124,7 +123,7 @@ Must be implemented by subclass, computes the absolute coords of upper-left corn ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Must be implemented by subclass, computes the absolute coords of bottom-right corner @@ -138,7 +137,7 @@ Must be implemented by subclass, computes the absolute coords of bottom-right co ### get_absolute_dimensions ```python -def get_absolute_dimensions(self) +def get_absolute_dimensions(self) -> Tuple[int,int] ``` Gets dimensions of element in terminal characters @@ -159,7 +158,7 @@ Gets dimensions of element in terminal characters ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Function that refreshes position and dimensons on resize. @@ -175,7 +174,7 @@ If necessary, make sure required widget attributes updated here as well. ### get_viewport_height ```python -def get_viewport_height(self) +def get_viewport_height(self) -> int ``` Gets the height of the element viewport (height minus padding and borders) @@ -196,7 +195,7 @@ Gets the height of the element viewport (height minus padding and borders) ### get_id ```python -def get_id(self) +def get_id(self) -> int ``` Gets the element ID @@ -217,7 +216,7 @@ Gets the element ID ### get_title ```python -def get_title(self) +def get_title(self) -> str ``` Getter for ui element title @@ -238,7 +237,7 @@ Getter for ui element title ### get_padding ```python -def get_padding(self) +def get_padding(self) -> Tuple[int,int] ``` Gets ui element padding on in characters @@ -259,7 +258,7 @@ Gets ui element padding on in characters ### get_start_position ```python -def get_start_position(self) +def get_start_position(self) -> Tuple[int,int] ``` Gets coords of upper left corner @@ -280,7 +279,7 @@ Gets coords of upper left corner ### get_stop_position ```python -def get_stop_position(self) +def get_stop_position(self) -> Tuple[int,int] ``` Gets coords of lower right corner @@ -301,7 +300,7 @@ Gets coords of lower right corner ### get_color ```python -def get_color(self) +def get_color(self) -> int ``` Gets current element color @@ -322,7 +321,7 @@ Gets current element color ### get_border_color ```python -def get_border_color(self) +def get_border_color(self) -> int ``` Gets current element border color @@ -343,7 +342,7 @@ Gets current element border color ### get_selected_color ```python -def get_selected_color(self) +def get_selected_color(self) -> int ``` Gets current selected item color @@ -364,7 +363,7 @@ Gets current selected item color ### is_selected ```python -def is_selected(self) +def is_selected(self) -> bool ``` Get selected status @@ -385,7 +384,7 @@ Get selected status ### get_renderer ```python -def get_renderer(self) +def get_renderer(self) -> 'py_cui.renderer.Renderer' ``` Gets reference to renderer object @@ -406,7 +405,7 @@ Gets reference to renderer object ### get_help_text ```python -def get_help_text(self) +def get_help_text(self) -> str ``` Returns current help text @@ -427,7 +426,7 @@ Returns current help text ### set_title ```python -def set_title(self, title) +def set_title(self, title: str) ``` Function that sets the widget title. @@ -448,7 +447,7 @@ Function that sets the widget title. ### set_color ```python -def set_color(self, color) +def set_color(self, color: int) -> None ``` Sets element default color @@ -469,7 +468,7 @@ Sets element default color ### set_border_color ```python -def set_border_color(self, color) +def set_border_color(self, color: int) -> None ``` Sets element border color @@ -490,7 +489,7 @@ Sets element border color ### set_focus_border_color ```python -def set_focus_border_color(self, color) +def set_focus_border_color(self, color: int) -> None ``` Sets element border color if the current element @@ -512,7 +511,7 @@ is focused ### set_selected_color ```python -def set_selected_color(self, color) +def set_selected_color(self, color: int) -> None ``` Sets element sected color @@ -533,7 +532,7 @@ Sets element sected color ### set_selected ```python -def set_selected(self, selected) +def set_selected(self, selected: bool) -> None ``` Marks the UI element as selected or not selected @@ -554,7 +553,7 @@ Marks the UI element as selected or not selected ### set_help_text ```python -def set_help_text(self, help_text) +def set_help_text(self, help_text: str) -> None ``` Sets status bar help text @@ -575,7 +574,7 @@ Sets status bar help text ### set_focus_text ```python -def set_focus_text(self, focus_text) +def set_focus_text(self, focus_text: str) -> None ``` Sets status bar focus text. Legacy function, overridden by set_focus_text @@ -607,31 +606,10 @@ Must be implemented by subclass. Used to handle keypresses -### add_mouse_press_handler - -```python -def add_mouse_press_handler(self, mouse_press_handler_func) -``` - -Sets a mouse press handler function - - - - -#### Parameters - - Parameter | Type | Doc ------|----------|----- - mouse_press_handler_func | function / lambda function | Function that takes 2 parameters: x and y of a mouse press. Executes when mouse pressed and element is selected - - - - - ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x, y, mouse_event) ``` Can be implemented by subclass. Used to handle mouse presses @@ -666,7 +644,7 @@ Must be implemented by subclasses. Uses renderer to draw element to terminal ### _assign_renderer ```python -def _assign_renderer(self, renderer) +def _assign_renderer(self, renderer: 'py_cui.renderer.Renderer', quiet: bool=False) ``` Function that assigns a renderer object to the element @@ -695,7 +673,7 @@ Function that assigns a renderer object to the element ### _contains_position ```python -def _contains_position(self, x, y) +def _contains_position(self, x: int, y: int) -> bool ``` Checks if character position is within element. @@ -813,7 +791,7 @@ UI implementation for a single-row textbox input ### __init__ ```python -def __init__(self, initial_text, password, logger) +def __init__(self, initial_text: str, password: bool , logger) ``` Initializer for the TextBoxImplementation base class @@ -827,7 +805,7 @@ Initializer for the TextBoxImplementation base class ### get_initial_cursor_pos ```python -def get_initial_cursor_pos(self) +def get_initial_cursor_pos(self) -> int ``` Gets initial cursor position @@ -848,7 +826,7 @@ Gets initial cursor position ### get_cursor_text_pos ```python -def get_cursor_text_pos(self) +def get_cursor_text_pos(self) -> int ``` Gets current position of cursor relative to text @@ -869,7 +847,7 @@ Gets current position of cursor relative to text ### get_cursor_limits ```python -def get_cursor_limits(self) +def get_cursor_limits(self) -> Tuple[int,int] ``` Gets cursor extreme points in terminal position @@ -890,7 +868,7 @@ Gets cursor extreme points in terminal position ### get_cursor_position ```python -def get_cursor_position(self) +def get_cursor_position(self) -> Tuple[int,int] ``` Returns current cursor poition @@ -911,7 +889,7 @@ Returns current cursor poition ### get_viewport_width ```python -def get_viewport_width(self) +def get_viewport_width(self) -> int ``` Gets the width of the textbox viewport @@ -932,7 +910,7 @@ Gets the width of the textbox viewport ### set_text ```python -def set_text(self, text) +def set_text(self, text: str) ``` Sets the value of the text. Overwrites existing text @@ -953,7 +931,7 @@ Sets the value of the text. Overwrites existing text ### get ```python -def get(self) +def get(self) -> str ``` Gets value of the text in the textbox @@ -974,7 +952,7 @@ Gets value of the text in the textbox ### clear ```python -def clear(self) +def clear(self) -> None ``` Clears the text in the textbox @@ -988,7 +966,7 @@ Clears the text in the textbox ### _move_left ```python -def _move_left(self) +def _move_left(self) -> None ``` Shifts the cursor the the left. Internal use only @@ -1002,7 +980,7 @@ Shifts the cursor the the left. Internal use only ### _move_right ```python -def _move_right(self) +def _move_right(self) -> None ``` Shifts the cursor the the right. Internal use only @@ -1016,7 +994,7 @@ Shifts the cursor the the right. Internal use only ### _insert_char ```python -def _insert_char(self, key_pressed) +def _insert_char(self, key_pressed: int) -> None ``` Inserts char at cursor position. Internal use only @@ -1037,7 +1015,7 @@ Inserts char at cursor position. Internal use only ### _jump_to_start ```python -def _jump_to_start(self) +def _jump_to_start(self) -> None ``` Jumps to the start of the textbox. Internal use only @@ -1051,7 +1029,7 @@ Jumps to the start of the textbox. Internal use only ### _jump_to_end ```python -def _jump_to_end(self) +def _jump_to_end(self) -> None ``` Jumps to the end to the textbox. Internal use only @@ -1065,7 +1043,7 @@ Jumps to the end to the textbox. Internal use only ### _erase_char ```python -def _erase_char(self) +def _erase_char(self) -> None ``` Erases character at textbox cursor. Internal Use only @@ -1079,7 +1057,7 @@ Erases character at textbox cursor. Internal Use only ### _delete_char ```python -def _delete_char(self) +def _delete_char(self) -> None ``` Deletes character to right of texbox cursor. Internal use only @@ -1120,7 +1098,8 @@ Analogous to a RadioButton Method | Doc -----|----- clear | Clears all items from the Scroll Menu - get_selected_item_index | Gets the currently selected item + 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""" 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 @@ -1156,7 +1135,7 @@ Initializer for MenuImplementation base class ### clear ```python -def clear(self) +def clear(self) -> None ``` Clears all items from the Scroll Menu @@ -1167,16 +1146,68 @@ Clears all items from the Scroll Menu -### get_selected_item_index +### set_on_selection_change_event + +```python +def set_on_selection_change_event(self, on_selection_change_event: Callable[[Any],Any]) +``` + +Function that sets the function fired when menu selection changes. + + + +Event function must take 0 or 1 parameters. If 1 parameter, the new selcted item will be passed in. + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + on_selection_change_event | Callable | Callable function that takes in as an argument the newly selected element + +#### Raises + + Error | Type | Doc +-----|----------|----- + Unknown | TypeError | Raises a type error if event function is not callable + + + + + +### _process_selection_change_event ```python -def get_selected_item_index(self) +def _process_selection_change_event(self) ``` -Gets the currently selected item +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 +try: +num_args = len(inspect.signature(self._on_selection_change).parameters) +except ValueError: +self._logger.error('Failed to get on_selection_change signature!') +except TypeError: +self._logger.error('Type of object not supported for signature identification!') + +# Depending on the number of parameters for the self._on_selection_change, pass in the x and y +# values, or do nothing +if num_args == 1: +self._on_selection_change(self.get()) +elif num_args == 0: +self._on_selection_change() +else: +raise ValueError('On selection change event must accept either 0 or 1 parameters!') + + +def get_selected_item_index(self) -> int: +"""Gets the currently selected item + #### Returns @@ -1191,7 +1222,7 @@ Gets the currently selected item ### set_selected_item_index ```python -def set_selected_item_index(self, selected_item_index) +def set_selected_item_index(self, selected_item_index: int) -> None ``` Sets the currently selected item @@ -1212,7 +1243,7 @@ Sets the currently selected item ### _scroll_up ```python -def _scroll_up(self) +def _scroll_up(self) -> None ``` Function that scrolls the view up in the scroll menu @@ -1226,7 +1257,7 @@ Function that scrolls the view up in the scroll menu ### _scroll_down ```python -def _scroll_down(self, viewport_height) +def _scroll_down(self, viewport_height: int) -> None ``` Function that scrolls the view down in the scroll menu @@ -1249,7 +1280,7 @@ TODO: Viewport height should be calculated internally, and not rely on a paramet ### _jump_up ```python -def _jump_up(self) +def _jump_up(self) -> None ``` Function for jumping up menu several spots at a time @@ -1263,7 +1294,7 @@ Function for jumping up menu several spots at a time ### _jump_down ```python -def _jump_down(self, viewport_height) +def _jump_down(self, viewport_height: int) -> None ``` Function for jumping down the menu several spots at a time @@ -1284,7 +1315,7 @@ Function for jumping down the menu several spots at a time ### _jump_to_top ```python -def _jump_to_top(self) +def _jump_to_top(self) -> None ``` Function that jumps to the top of the menu @@ -1298,7 +1329,7 @@ Function that jumps to the top of the menu ### _jump_to_bottom ```python -def _jump_to_bottom(self, viewport_height) +def _jump_to_bottom(self, viewport_height: int) -> None ``` Function that jumps to the bottom of the menu @@ -1319,7 +1350,7 @@ Function that jumps to the bottom of the menu ### add_item ```python -def add_item(self, item) +def add_item(self, item: Any) -> None ``` Adds an item to the menu. @@ -1340,7 +1371,7 @@ Adds an item to the menu. ### add_item_list ```python -def add_item_list(self, item_list) +def add_item_list(self, item_list: List[Any]) -> None ``` Adds a list of items to the scroll menu. @@ -1361,7 +1392,7 @@ Adds a list of items to the scroll menu. ### remove_selected_item ```python -def remove_selected_item(self) +def remove_selected_item(self) -> None ``` Function that removes the selected item from the scroll menu. @@ -1375,7 +1406,7 @@ Function that removes the selected item from the scroll menu. ### remove_item ```python -def remove_item(self, item) +def remove_item(self, item: Any) -> None ``` Function that removes a specific item from the menu @@ -1396,7 +1427,7 @@ Function that removes a specific item from the menu ### get_item_list ```python -def get_item_list(self) +def get_item_list(self) -> List[Any] ``` Function that gets list of items in a scroll menu @@ -1417,7 +1448,7 @@ Function that gets list of items in a scroll menu ### get ```python -def get(self) +def get(self) -> Optional[Any] ``` Function that gets the selected item from the scroll menu @@ -1438,7 +1469,7 @@ Function that gets the selected item from the scroll menu ### set_selected_item ```python -def set_selected_item(self, selected_item) +def set_selected_item(self, selected_item: Any) ``` Function that replaces the currently selected item with a new item @@ -1484,7 +1515,9 @@ Class representing checkbox menu ui implementation add_item | Extends base class function, item is added and marked as unchecked to start remove_selected_item | Removes selected item from item list and selected item dictionary remove_item | Removes item from item list and selected item dict + toggle_item_checked | Function that marks an item as selected mark_item_as_checked | Function that marks an item as selected + mark_item_as_not_checked | Function that marks an item as selected @@ -1506,7 +1539,7 @@ Initializer for the checkbox menu implementation ### add_item ```python -def add_item(self, item) +def add_item(self, item: Any) -> None ``` Extends base class function, item is added and marked as unchecked to start @@ -1527,7 +1560,7 @@ Extends base class function, item is added and marked as unchecked to start ### remove_selected_item ```python -def remove_selected_item(self) +def remove_selected_item(self) -> None ``` Removes selected item from item list and selected item dictionary @@ -1541,7 +1574,7 @@ Removes selected item from item list and selected item dictionary ### remove_item ```python -def remove_item(self, item) +def remove_item(self, item) -> None ``` Removes item from item list and selected item dict @@ -1559,10 +1592,52 @@ Removes item from item list and selected item dict +### toggle_item_checked + +```python +def toggle_item_checked(self, item: Any) +``` + +Function that marks an item as selected + + + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + item | object | Toggle item checked state + + + + + ### mark_item_as_checked ```python -def mark_item_as_checked(self, item) +def mark_item_as_checked(self, item: Any) -> None +``` + +Function that marks an item as selected + + + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + item | object | Toggle item checked state + + + + + +### mark_item_as_not_checked + +```python +def mark_item_as_not_checked(self, item) -> None ``` Function that marks an item as selected @@ -1574,7 +1649,7 @@ Function that marks an item as selected Parameter | Type | Doc -----|----------|----- - item | object | Mark item as checked + item | object | Item to uncheck @@ -1642,7 +1717,7 @@ Currently only implemented in widget form, though popup form is possible. ### __init__ ```python -def __init__(self, initial_text, logger) +def __init__(self, initial_text: str, logger) ``` Initializer for TextBlockImplementation base class @@ -1658,7 +1733,7 @@ Zeros attributes, and parses initial text ### get_viewport_start_pos ```python -def get_viewport_start_pos(self) +def get_viewport_start_pos(self) -> Tuple[int,int] ``` Gets upper left corner position of viewport @@ -1679,7 +1754,7 @@ Gets upper left corner position of viewport ### get_viewport_dims ```python -def get_viewport_dims(self) +def get_viewport_dims(self) -> Tuple[int,int] ``` Gets viewport dimensions in characters @@ -1700,7 +1775,7 @@ Gets viewport dimensions in characters ### get_cursor_text_pos ```python -def get_cursor_text_pos(self) +def get_cursor_text_pos(self) -> Tuple[int,int] ``` Gets cursor postion relative to text @@ -1721,7 +1796,7 @@ Gets cursor postion relative to text ### get_abs_cursor_position ```python -def get_abs_cursor_position(self) +def get_abs_cursor_position(self) -> Tuple[int,int] ``` Gets absolute cursor position in terminal characters @@ -1742,7 +1817,7 @@ Gets absolute cursor position in terminal characters ### get_cursor_limits_vertical ```python -def get_cursor_limits_vertical(self) +def get_cursor_limits_vertical(self) -> Tuple[int,int] ``` Gets limits for cursor in vertical direction @@ -1763,7 +1838,7 @@ Gets limits for cursor in vertical direction ### get_cursor_limits_horizontal ```python -def get_cursor_limits_horizontal(self) +def get_cursor_limits_horizontal(self) -> Tuple[int,int] ``` Gets limits for cursor in horizontal direction @@ -1784,7 +1859,7 @@ Gets limits for cursor in horizontal direction ### get ```python -def get(self) +def get(self) -> str ``` Gets all of the text in the textblock and returns it @@ -1805,7 +1880,7 @@ Gets all of the text in the textblock and returns it ### write ```python -def write(self, text) +def write(self, text: str) -> None ``` Function used for writing text to the text block @@ -1826,7 +1901,7 @@ Function used for writing text to the text block ### clear ```python -def clear(self) +def clear(self) -> None ``` Function that clears the text block @@ -1840,7 +1915,7 @@ Function that clears the text block ### get_current_line ```python -def get_current_line(self) +def get_current_line(self) -> str ``` Returns the line on which the cursor currently resides @@ -1861,7 +1936,7 @@ Returns the line on which the cursor currently resides ### set_text ```python -def set_text(self, text) +def set_text(self, text: str) -> None ``` Function that sets the text for the textblock. @@ -1884,7 +1959,7 @@ Note that this will overwrite any existing text ### set_text_line ```python -def set_text_line(self, text) +def set_text_line(self, text: str) -> None ``` Function that sets the current line's text. @@ -1907,7 +1982,7 @@ Meant only for internal use ### _move_left ```python -def _move_left(self) +def _move_left(self) -> None ``` Function that moves the cursor/text position one location to the left @@ -1921,7 +1996,7 @@ Function that moves the cursor/text position one location to the left ### _move_right ```python -def _move_right(self) +def _move_right(self) -> None ``` Function that moves the cursor/text position one location to the right @@ -1935,7 +2010,7 @@ Function that moves the cursor/text position one location to the right ### _move_up ```python -def _move_up(self) +def _move_up(self) -> None ``` Function that moves the cursor/text position one location up @@ -1949,7 +2024,7 @@ Function that moves the cursor/text position one location up ### _move_down ```python -def _move_down(self) +def _move_down(self) -> None ``` Function that moves the cursor/text position one location down @@ -1963,7 +2038,7 @@ Function that moves the cursor/text position one location down ### _handle_newline ```python -def _handle_newline(self) +def _handle_newline(self) -> None ``` Function that handles recieving newline characters in the text @@ -1977,7 +2052,7 @@ Function that handles recieving newline characters in the text ### _handle_backspace ```python -def _handle_backspace(self) +def _handle_backspace(self) -> None ``` Function that handles recieving backspace characters in the text @@ -1991,7 +2066,7 @@ Function that handles recieving backspace characters in the text ### _handle_home ```python -def _handle_home(self) +def _handle_home(self) -> None ``` Function that handles recieving a home keypress @@ -2005,7 +2080,7 @@ Function that handles recieving a home keypress ### _handle_end ```python -def _handle_end(self) +def _handle_end(self) -> None ``` Function that handles recieving an end keypress @@ -2019,7 +2094,7 @@ Function that handles recieving an end keypress ### _handle_delete ```python -def _handle_delete(self) +def _handle_delete(self) -> None ``` Function that handles recieving a delete keypress @@ -2033,7 +2108,7 @@ Function that handles recieving a delete keypress ### _insert_char ```python -def _insert_char(self, key_pressed) +def _insert_char(self, key_pressed: int) -> None ``` Function that handles recieving a character diff --git a/docs/DocstringGenerated/WidgetSet.md b/docs/DocstringGenerated/WidgetSet.md index 3ae788b..0daf681 100644 --- a/docs/DocstringGenerated/WidgetSet.md +++ b/docs/DocstringGenerated/WidgetSet.md @@ -36,6 +36,7 @@ Use PyCUI.apply_widget_set() to set a given widget set for display widgets | dict of str - py_cui.widgets.Widget | dict of widget in the grid keybindings | list of py_cui.keybinding.KeyBinding | list of keybindings to check against in the main CUI loop height, width | int | height of the terminal in characters, width of terminal in characters + root | py_cui.PyCUI | Main PyCUI object reference #### Methods @@ -59,7 +60,7 @@ Use PyCUI.apply_widget_set() to set a given widget set for display ### __init__ ```python -def __init__(self, num_rows, num_cols, logger, simulated_terminal=None) +def __init__(self, num_rows: int, num_cols: int, logger: 'py_cui.debug.PyCUILogger', root:'py_cui.PyCUI', simulated_terminal: Optional[List[int]] =None) ``` Constructor for WidgetSet @@ -73,7 +74,7 @@ Constructor for WidgetSet ### set_selected_widget ```python -def set_selected_widget(self, widget_id) +def set_selected_widget(self, widget_id: int) -> None ``` Function that sets the selected cell for the CUI @@ -94,7 +95,7 @@ Function that sets the selected cell for the CUI ### get_widgets ```python -def get_widgets(self) +def get_widgets(self) -> Dict[int, Optional['py_cui.widgets.Widget']] ``` Function that gets current set of widgets @@ -115,7 +116,7 @@ Function that gets current set of widgets ### add_key_command ```python -def add_key_command(self, key, command) +def add_key_command(self, key: int, command: Callable[[],Any]) ``` Function that adds a keybinding to the CUI when in overview mode @@ -137,7 +138,7 @@ Function that adds a keybinding to the CUI when in overview mode ### add_scroll_menu ```python -def add_scroll_menu(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0) +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 @@ -170,7 +171,7 @@ Function that adds a new scroll menu to the CUI grid ### add_checkbox_menu ```python -def add_checkbox_menu(self, title, row, column, row_span=1, column_span=1, padx=1, pady=0, checked_char='X') +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 @@ -204,7 +205,7 @@ Function that adds a new checkbox menu to the CUI grid ### add_text_box ```python -def add_text_box(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = '', password = False) +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 @@ -239,7 +240,7 @@ Function that adds a new text box to the CUI grid ### add_text_block ```python -def add_text_block(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, initial_text = '') +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 @@ -273,7 +274,7 @@ Function that adds a new text block to the CUI grid ### add_label ```python -def add_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0) +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 @@ -306,7 +307,7 @@ Function that adds a new label to the CUI grid ### add_block_label ```python -def add_block_label(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, center=True) +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 @@ -340,7 +341,7 @@ Function that adds a new block label to the CUI grid ### add_button ```python -def add_button(self, title, row, column, row_span = 1, column_span = 1, padx = 1, pady = 0, command=None) +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' ``` Function that adds a new button to the CUI grid @@ -374,7 +375,7 @@ Function that adds a new button to the CUI grid ### add_slider ```python -def add_slider(self, title, row, column, row_span=1 +def add_slider(self, title: str, row: int, column: int, row_span: int=1 ``` Function that adds a new label to the CUI grid diff --git a/docs/DocstringGenerated/Widgets.md b/docs/DocstringGenerated/Widgets.md index 487074c..4899010 100644 --- a/docs/DocstringGenerated/Widgets.md +++ b/docs/DocstringGenerated/Widgets.md @@ -1,6 +1,6 @@ # widgets -Module contatining all core widget classes for py_cui. +Module containing all core widget classes for py_cui. @@ -66,6 +66,7 @@ and setting status bar text. Method | Doc -----|----- add_key_command | Maps a keycode to a function that will be executed when in focus mode + add_mouse_command | Maps a keycode to a function that will be executed when in focus mode update_key_command | Maps a keycode to a function that will be executed when in focus mode, if key is already mapped add_text_color_rule | Forces renderer to draw text using given color if text_condition_function returns True get_absolute_start_pos | Gets the absolute position of the widget in characters. Override of base class function @@ -75,6 +76,7 @@ and setting status bar text. set_selectable | Setter for widget selectablility is_selectable | Checks if the widget is selectable _is_row_col_inside | Checks if a particular row + column is inside the widget area + _handle_mouse_press | Base class function that handles all assigned mouse presses. _handle_key_press | Base class function that handles all assigned key presses. _draw | Base class draw class that checks if renderer is valid. @@ -84,14 +86,14 @@ and setting status bar text. ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, selectable = True) +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, selectable: bool = True) ``` Initializer for base widget class -Calss UIElement superclass initialzier, and then assigns widget to grid, along with row/column info +Class UIElement superclass initializer, and then assigns widget to grid, along with row/column info and color rules and key commands @@ -101,7 +103,7 @@ and color rules and key commands ### add_key_command ```python -def add_key_command(self, key, command) +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 @@ -120,10 +122,38 @@ Maps a keycode to a function that will be executed when in focus mode +### add_mouse_command + +```python +def add_mouse_command(self, mouse_event: int, command: Callable[[],Any]) -> None +``` + +Maps a keycode to a function that will be executed when in focus mode + + + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + key | py_cui.keys.MOUSE_EVENT | Mouse event code from py_cui.keys + command | Callable | a non-argument function or lambda function to execute if in focus mode and key is pressed + +#### Raises + + Error | Type | Doc +-----|----------|----- + Unknown | PyCUIError | If input mouse event code is not valid + + + + + ### update_key_command ```python -def update_key_command(self, key, command) +def update_key_command(self, key: int, command: Callable[[],Any]) -> Any ``` Maps a keycode to a function that will be executed when in focus mode, if key is already mapped @@ -145,7 +175,7 @@ Maps a keycode to a function that will be executed when in focus mode, if key is ### add_text_color_rule ```python -def add_text_color_rule(self, regex, color, rule_type, match_type='line', region=[0,1], include_whitespace=False, selected_color=None) +def add_text_color_rule(self, regex: str, color: int, rule_type: str, match_type: str='line', region: List[int]=[0,1], include_whitespace: bool=False, selected_color=None) -> None ``` Forces renderer to draw text using given color if text_condition_function returns True @@ -171,7 +201,7 @@ Forces renderer to draw text using given color if text_condition_function return ### get_absolute_start_pos ```python -def get_absolute_start_pos(self) +def get_absolute_start_pos(self) -> Tuple[int,int] ``` Gets the absolute position of the widget in characters. Override of base class function @@ -192,7 +222,7 @@ Gets the absolute position of the widget in characters. Override of base class f ### get_absolute_stop_pos ```python -def get_absolute_stop_pos(self) +def get_absolute_stop_pos(self) -> Tuple[int,int] ``` Gets the absolute dimensions of the widget in characters. Override of base class function @@ -213,7 +243,7 @@ Gets the absolute dimensions of the widget in characters. Override of base class ### get_grid_cell ```python -def get_grid_cell(self) +def get_grid_cell(self) -> Tuple[int,int] ``` Gets widget row, column in grid @@ -234,7 +264,7 @@ Gets widget row, column in grid ### get_grid_cell_spans ```python -def get_grid_cell_spans(self) +def get_grid_cell_spans(self) -> Tuple[int,int] ``` Gets widget row span, column span in grid @@ -255,7 +285,7 @@ Gets widget row span, column span in grid ### set_selectable ```python -def set_selectable(self, selectable) +def set_selectable(self, selectable: bool) -> None ``` Setter for widget selectablility @@ -274,7 +304,7 @@ Widget selectable if true, otherwise not ### is_selectable ```python -def is_selectable(self) +def is_selectable(self) -> bool ``` Checks if the widget is selectable @@ -295,7 +325,7 @@ Checks if the widget is selectable ### _is_row_col_inside ```python -def _is_row_col_inside(self, row, col) +def _is_row_col_inside(self, row: int, col: int) -> bool ``` Checks if a particular row + column is inside the widget area @@ -319,10 +349,34 @@ Checks if a particular row + column is inside the widget area +### _handle_mouse_press + +```python +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) +``` + +Base class function that handles all assigned mouse presses. + + + +When overwriting this function, make sure to add a super()._handle_mouse_press(x, y, mouse_event) call, +as this is required for user defined key command support + + +#### Parameters + + Parameter | Type | Doc +-----|----------|----- + key_pressed | int | key code of key pressed + + + + + ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Base class function that handles all assigned key presses. @@ -346,7 +400,7 @@ as this is required for user defined key command support ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Base class draw class that checks if renderer is valid. @@ -395,7 +449,7 @@ Simply displays one centered row of text. Has no unique attributes or methods ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, 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) ``` Initalizer for Label widget @@ -409,7 +463,7 @@ Initalizer for Label widget ### toggle_border ```python -def toggle_border(self) +def toggle_border(self) -> None ``` Function that gives option to draw border around label @@ -423,7 +477,7 @@ Function that gives option to draw border around label ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override base draw class. @@ -471,7 +525,7 @@ A Variation of the label widget that renders a block of text. ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, 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, center, logger) ``` Initializer for blocklabel widget @@ -485,7 +539,7 @@ Initializer for blocklabel widget ### set_title ```python -def set_title(self, title) +def set_title(self, title: str) -> None ``` Override of base class, splits title into lines for rendering line by line. @@ -506,7 +560,7 @@ Override of base class, splits title into lines for rendering line by line. ### toggle_border ```python -def toggle_border(self) +def toggle_border(self) -> None ``` Function that gives option to draw border around label @@ -520,7 +574,7 @@ Function that gives option to draw border around label ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override base draw class. @@ -560,7 +614,7 @@ A scroll menu widget. ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, 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') ``` Initializer for scroll menu. calls superclass initializers and sets help text @@ -574,7 +628,7 @@ Initializer for scroll menu. calls superclass initializers and sets help text ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) ``` Override of base class function, handles mouse press in menu @@ -595,7 +649,7 @@ Override of base class function, handles mouse press in menu ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override base class function. @@ -618,7 +672,7 @@ UP_ARROW scrolls up, DOWN_ARROW scrolls down. ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Overrides base class draw function @@ -664,7 +718,7 @@ Extension of ScrollMenu that allows for multiple items to be selected at once. ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, checked_char) +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, checked_char: str) ``` Initializer for CheckBoxMenu Widget @@ -678,7 +732,7 @@ Initializer for CheckBoxMenu Widget ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None ``` Override of base class function, handles mouse press in menu @@ -699,7 +753,7 @@ Override of base class function, handles mouse press in menu ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of key presses. @@ -723,7 +777,7 @@ Adds Enter command to toggle selection ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Overrides base class draw function @@ -769,7 +823,7 @@ Allows for running a command function on Enter ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, command) +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, command: Optional[Callable[[],Any]]) ``` Initializer for Button Widget @@ -783,7 +837,7 @@ Initializer for Button Widget ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base class, adds ENTER listener that runs the button's command @@ -804,7 +858,7 @@ Override of base class, adds ENTER listener that runs the button's command ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base class draw function @@ -843,7 +897,7 @@ Widget for entering small single lines of text ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, initial_text, password) +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, initial_text: str, password: bool) ``` Initializer for TextBox widget. Uses TextBoxImplementation as base @@ -857,7 +911,7 @@ Initializer for TextBox widget. Uses TextBoxImplementation as base ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Need to update all cursor positions on resize @@ -871,7 +925,7 @@ Need to update all cursor positions on resize ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None ``` Override of base class function, handles mouse press in menu @@ -892,7 +946,7 @@ Override of base class function, handles mouse press in menu ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base handle key press function @@ -913,7 +967,7 @@ Override of base handle key press function ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base draw function @@ -952,7 +1006,7 @@ Widget for editing large multi-line blocks of text ### __init__ ```python -def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, logger, initial_text) +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, initial_text: str) ``` Initializer for TextBlock Widget. Uses TextBlockImplementation as base @@ -966,7 +1020,7 @@ Initializer for TextBlock Widget. Uses TextBlockImplementation as base ### update_height_width ```python -def update_height_width(self) +def update_height_width(self) -> None ``` Function that updates the position of the text and cursor on resize @@ -980,7 +1034,7 @@ Function that updates the position of the text and cursor on resize ### _handle_mouse_press ```python -def _handle_mouse_press(self, x, y) +def _handle_mouse_press(self, x: int, y: int, mouse_event: int) -> None ``` Override of base class function, handles mouse press in menu @@ -1001,7 +1055,7 @@ Override of base class function, handles mouse press in menu ### _handle_key_press ```python -def _handle_key_press(self, key_pressed) +def _handle_key_press(self, key_pressed: int) -> None ``` Override of base class handle key press function @@ -1022,7 +1076,7 @@ Override of base class handle key press function ### _draw ```python -def _draw(self) +def _draw(self) -> None ``` Override of base class draw function From d6018bfa96fb7bce11001e42816f8b2c30fa30e0 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Fri, 29 Oct 2021 16:36:03 -0400 Subject: [PATCH 39/40] Replace mark as checked with toggle function for checkbox enter key callback --- py_cui/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py_cui/widgets.py b/py_cui/widgets.py index 56b38d4..f119527 100644 --- a/py_cui/widgets.py +++ b/py_cui/widgets.py @@ -619,7 +619,7 @@ def _handle_key_press(self, key_pressed: int) -> None: if key_pressed == py_cui.keys.KEY_PAGE_DOWN: self._jump_down(viewport_height) if key_pressed == py_cui.keys.KEY_ENTER: - self.mark_item_as_checked(self.get()) + self.toggle_item_checked(self.get()) def _draw(self) -> None: From f16205ce47b4f12f5b3ca21178ee273ab11bddc1 Mon Sep 17 00:00:00 2001 From: jwlodek Date: Fri, 29 Oct 2021 16:42:48 -0400 Subject: [PATCH 40/40] Remove unnecessary superclass function calls in filedialog --- py_cui/dialogs/filedialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py_cui/dialogs/filedialog.py b/py_cui/dialogs/filedialog.py index 0b5b46c..3a9d4c2 100644 --- a/py_cui/dialogs/filedialog.py +++ b/py_cui/dialogs/filedialog.py @@ -244,7 +244,7 @@ def _handle_key_press(self, key_pressed: int) -> None: key code of key pressed """ - super()._handle_key_press(key_pressed) + #super()._handle_key_press(key_pressed) if key_pressed == py_cui.keys.KEY_ENTER: old_dir = self._current_dir item = self.get() @@ -564,7 +564,7 @@ def _draw(self) -> None: """Override of base class draw function """ - super()._draw() + #super()._draw() self._renderer.set_color_mode(self.get_color()) self._renderer.draw_border(self, with_title=False) button_text_y_pos = self._start_y + int(self._height / 2)