"""A python library for intuitively creating CUI/TUI interfaces with pre-built widgets.
"""

#
# Author:   Jakub Wlodek
# Created:  12-Aug-2019
# Docs:     https://jwlodek.github.io/py_cui-docs
# License:  BSD-3-Clause (New/Revised)
#

# Some python core library imports
import sys
import os
import time
import copy
import shutil       # We use shutil for getting the terminal dimensions
import threading    # Threading is used for loading icon popups
import logging      # Use logging library for debug purposes


# py_cui uses the curses library. On windows this does not exist, but
# there is a open source windows-curses module that adds curses support
# for python on windows
import curses


# py_cui imports
import py_cui
import py_cui.keys
import py_cui.statusbar
import py_cui.widgets
import py_cui.controls
import py_cui.dialogs
import py_cui.widget_set
import py_cui.popups
import py_cui.renderer
import py_cui.debug
import py_cui.errors
from py_cui.colors import *



# Version number
__version__ = '0.1.3'


def fit_text(width, text, center=False):
    """Fits text to screen size

    Helper function to fit text within a given width. Used to fix issue with status/title bar text
    being too long

    Parameters
    ----------
    width : int
        width of window in characters
    text : str
        input text
    center : Boolean
        flag to center text

    Returns
    -------
    fitted_text : str
        text fixed depending on width
    """

    if width < 5:
        return '.' * width
    if len(text) >= width:
        return text[:width - 5] + '...'
    else:
        total_num_spaces = (width - len(text) - 1)
        if center:
            left_spaces = int(total_num_spaces / 2)
            right_spaces = int(total_num_spaces / 2)
            if(total_num_spaces % 2 == 1):
                right_spaces = right_spaces + 1
            return ' ' * left_spaces + text + ' ' * right_spaces
        else:
            return text + ' ' * total_num_spaces


class PyCUI:
    """Base CUI class

    Main user interface class for py_cui. To create a user interface, you must
    first create an instance of this class, and then add cells + widgets to it.

    Attributes
    ----------
    cursor_x, cursor_y : int
        absolute position of the cursor in the CUI
    grid : py_cui.grid.Grid
        The main layout manager for the CUI
    widgets : dict of str - py_cui.widgets.Widget
        dict of widget in the grid
    title_bar : py_cui.statusbar.StatusBar
        a status bar object that gets drawn at the top of the CUI
    status_bar : py_cui.statusbar.StatusBar
        a status bar object that gets drawn at the bottom of the CUI
    keybindings : list of py_cui.keybinding.KeyBinding
        list of keybindings to check against in the main CUI loop
    height, width : int
        height of the terminal in characters, width of terminal in characters
    exit_key : key_code
        a key code for a key that exits the CUI
    simulated_terminal : List[int]
        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):
        """Constructor 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')

        # For unit testing purposes, we want to simulate terminal
        # dimensions so that we don't get errors
        self._simulated_terminal = simulated_terminal

        if self._simulated_terminal is None:
            term_size = shutil.get_terminal_size()
            height  = term_size.lines
            width   = term_size.columns
        else:
            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)
        exit_key_char = py_cui.keys.get_char_from_ascii(exit_key)
        self._init_status_bar_text  = 'Press - {} - to exit. Arrow Keys to move ' \
                                       'between widgets. Enter to enter focus ' \
                                       'mode.'.format(exit_key_char)
        self.status_bar = py_cui.statusbar.StatusBar(self._init_status_bar_text,
                                                     BLACK_ON_WHITE)

        # Logging object initialization for py_cui
        self._logger = py_cui.debug._initialize_logger(self,
                                                       name='py_cui')

        # Initialize grid, renderer, and widget dict
        self._grid                  = py_cui.grid.Grid(num_rows, num_cols, self._height, self._width, self._logger)
        self._renderer              = None
        self._border_characters     = None
        self._stdscr                = None
        self._widgets               = {}
        self._refresh_timeout       = -1

        # Variables for determining selected widget/focus mode
        self._selected_widget       = None
        self._in_focused_mode       = False
        self._popup                 = 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

        # Top level keybindings. Exit key is 'q' by default
        self._keybindings  = {}
        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

        # Callback to fire when CUI is stopped.
        self._on_stop = None


    def set_refresh_timeout(self, timeout):
        """Sets the CUI auto-refresh timeout to a number of seconds.

        Parameters
        ----------
        timeout : int
            Number of seconds to wait before refreshing the CUI
        """

        # We want the refresh timeout in milliseconds as an integer
        self._refresh_timeout = int(timeout * 1000)


    def set_on_draw_update_func(self, update_function):
        """Adds a function that is fired during each draw call of the CUI

        Parameters
        ----------
        update_function : function
            A no-argument or lambda function that is fired at the start of each draw call
        """

        self._on_draw_update_func = update_function


    def set_widget_cycle_key(self, forward_cycle_key=None, reverse_cycle_key=None):
        """Assigns a key for automatically cycling through widgets in both focus and overview modes

        Parameters
        ----------
        widget_cycle_key : py_cui.keys.KEY
            Key code for key to cycle through widgets
        """

        if forward_cycle_key is not None:
            self._forward_cycle_key = forward_cycle_key
        if reverse_cycle_key is not None:
            self._reverse_cycle_key = reverse_cycle_key


    def enable_logging(self, log_file_path='py_cui_log.txt', logging_level = logging.DEBUG):
        """Function enables logging for py_cui library

        Parameters
        ----------
        log_file_path : str
            The target log filepath. Default 'py_cui_log.txt
        logging_level : int
            Default logging level = logging.DEBUG
        """

        try:
            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)))


    def apply_widget_set(self, new_widget_set):
        """Function that replaces all widgets in a py_cui with those of a different widget set

        Parameters
        ----------
        new_widget_set : WidgetSet
            The new widget set to switch to

        Raises
        ------
        TypeError
            If input is not of type WidgetSet
        """

        if isinstance(new_widget_set, py_cui.widget_set.WidgetSet):
            self.lose_focus()
            self._widgets      = new_widget_set._widgets
            self._grid         = new_widget_set._grid
            self._keybindings  = new_widget_set._keybindings

            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)
            if self._stdscr is not None:
                self._initialize_widget_renderer()
            self._selected_widget = new_widget_set._selected_widget
        else:
            raise TypeError('Argument must be of type py_cui.widget_set.WidgetSet')


    def create_new_widget_set(self, num_rows, num_cols):
        """Function that is used to create additional widget sets

        Use this function instead of directly creating widget set object instances, to allow
        for logging support.

        Parameters
        ----------
        num_rows : int
            row count for new widget set
        num_cols : int
            column count for new widget set

        Returns
        -------
        new_widget_set : py_cui.widget_set.WidgetSet
            The new widget set object instance
        """

        # Use current logging object and simulated terminal for sub-widget sets
        return py_cui.widget_set.WidgetSet(num_rows, num_cols, self._logger, 
                                            simulated_terminal=self._simulated_terminal)


    # ----------------------------------------------#
    # Initialization functions                      #
    # Used to initialzie CUI and its features       #
    # ----------------------------------------------#


    def start(self):
        """Function that starts the CUI
        """

        self._logger.info('Starting {} CUI'.format(self._title))
        curses.wrapper(self._draw)


    def stop(self):
        """Function that stops the CUI, and fires the callback function.

        Callback must be a no arg method
        """

        self._logger.info('Stopping CUI')
        self._stopped = True


    def run_on_exit(self, command):
        """Sets callback function on CUI exit. Must be a no-argument function or lambda function

        Parameters
        ----------
        command : function
            A no-argument or lambda function to be fired on exit
        """

        self._on_stop = command


    def set_title(self, title):
        """Sets the title bar text

        Parameters
        ----------
        title : str
            New title for CUI
        """

        self._title = title


    def set_status_bar_text(self, text):
        """Sets the status bar text when in overview mode

        Parameters
        ----------
        text : str
            Status bar text
        """

        self._init_status_bar_text = text
        self.status_bar.set_text(text)


    def _initialize_colors(self):
        """Function for initialzing curses colors. Called when CUI is first created.
        """

        # Start colors in curses.
        # For each color pair in color map, initialize color combination.
        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)


    def _initialize_widget_renderer(self):
        """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._widgets.keys():
            self._widgets[widget_id]._assign_renderer(self._renderer)
        if self._popup is not None:
            self._popup._assign_renderer(self._renderer)


    def toggle_unicode_borders(self):
        """Function for toggling unicode based border rendering
        """

        if self._border_characters is None or self._border_characters['UP_LEFT'] == '+':
            self.set_widget_border_characters('\u256d', '\u256e', '\u2570', '\u256f', '\u2500', '\u2502')
        else:
            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):
        """Function that can be used to set arbitrary border characters for drawing widget borders by renderer.

        Parameters
        ----------
        upper_left_corner : char
            Upper left corner character
        upper_right_corner : char
            Upper right corner character
        lower_left_corner : char
            Upper left corner character
        lower_right_corner : char
            Lower right corner character
        horizontal : char
            Horizontal border character
        vertical : char
            Vertical border character
        """

        self._border_characters = {
            'UP_LEFT': upper_left_corner,
            'UP_RIGHT': upper_right_corner,
            'DOWN_LEFT': lower_left_corner,
            'DOWN_RIGHT': lower_right_corner,
            'HORIZONTAL': horizontal,
            'VERTICAL': vertical
        }
        self._logger.info('Set border_characters to {}'.format(self._border_characters))


    def get_widgets(self):
        """Function that gets current set of widgets

        Returns
        -------
        widgets : dict of str -> widget
            dictionary mapping widget IDs to object instances
        """

        return self._widgets

    # 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:
        """Function that adds a new scroll menu to the CUI grid

        Parameters
        ----------
        title : str
            The title of the scroll menu
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction

        Returns
        -------
        new_scroll_menu : ScrollMenu
            A reference to the created scroll menu object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_scroll_menu     = py_cui.widgets.ScrollMenu(id,
                                                 title,
                                                 self._grid,
                                                 row,
                                                 column,
                                                 row_span,
                                                 column_span,
                                                 padx,
                                                 pady,
                                                 self._logger)
        self._widgets[id]  = new_scroll_menu
        if self._selected_widget is None:
            self.set_selected_widget(id)
        self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_scroll_menu))))
        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:
        """Function that adds a new checkbox menu to the CUI grid

        Parameters
        ----------
        title : str
            The title of the checkbox
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction
        checked_char='X' : char
            The character used to mark 'Checked' items

        Returns
        -------
        new_checkbox_menu : CheckBoxMenu
            A reference to the created checkbox object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_checkbox_menu   = py_cui.widgets.CheckBoxMenu(id,
                                                   title,
                                                   self._grid,
                                                   row,
                                                   column,
                                                   row_span,
                                                   column_span,
                                                   padx,
                                                   pady,
                                                   self._logger,
                                                   checked_char)
        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))))
        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:
        """Function that adds a new text box to the CUI grid

        Parameters
        ----------
        title : str
            The title of the textbox
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction
        initial_text='' : str
            Initial text for the textbox
        password=False : bool
            Toggle to show '*' instead of characters.

        Returns
        -------
        new_text_box : TextBox
            A reference to the created textbox object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_text_box        = py_cui.widgets.TextBox(id,
                                              title,
                                              self._grid,
                                              row, column,
                                              row_span,
                                              column_span,
                                              padx, pady,
                                              self._logger,
                                              initial_text,
                                              password)
        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))))
        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:
        """Function that adds a new text block to the CUI grid

        Parameters
        ----------
        title : str
            The title of the text block
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction
        initial_text='' : str
            Initial text for the text block

        Returns
        -------
        new_text_block : ScrollTextBlock
            A reference to the created textblock object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_text_block      = py_cui.widgets.ScrollTextBlock(id,
                                                      title,
                                                      self._grid,
                                                      row,
                                                      column,
                                                      row_span,
                                                      column_span,
                                                      padx,
                                                      pady,
                                                      self._logger,
                                                      initial_text)
        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))))
        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:
        """Function that adds a new label to the CUI grid

        Parameters
        ----------
        title : str
            The title of the label
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction

        Returns
        -------
        new_label : Label
            A reference to the created label object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_label           = py_cui.widgets.Label(id,
                                            title,
                                            self._grid,
                                            row,
                                            column,
                                            row_span,
                                            column_span,
                                            padx,
                                            pady,
                                            self._logger)
        self._widgets[id]  = new_label
        self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_label))))
        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:
        """Function that adds a new block label to the CUI grid

        Parameters
        ----------
        title : str
            The title of the block label
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction
        center : bool
            flag to tell label to be centered or left-aligned.

        Returns
        -------
        new_label : BlockLabel
            A reference to the created block label object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_label           = py_cui.widgets.BlockLabel(id,
                                                 title,
                                                 self._grid,
                                                 row,
                                                 column,
                                                 row_span,
                                                 column_span,
                                                 padx,
                                                 pady,
                                                 center,
                                                 self._logger)
        self._widgets[id]  = new_label
        self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_label))))
        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:
        """Function that adds a new button to the CUI grid

        Parameters
        ----------
        title : str
            The title of the button
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction
        command=None : Function
            A no-argument or lambda function to fire on button press.

        Returns
        -------
        new_button : Button
            A reference to the created button object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_button          = py_cui.widgets.Button(id,
                                             title,
                                             self._grid,
                                             row,
                                             column,
                                             row_span,
                                             column_span,
                                             padx,
                                             pady,
                                             self._logger,
                                             command)
        self._widgets[id]  = new_button
        if self._selected_widget is None:
            self.set_selected_widget(id)
        self._logger.info('Adding widget {} w/ ID {} of type {}'.format(title, id, str(type(new_button))))
        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:
        """Function that adds a new label to the CUI grid

        Parameters
        ----------
        title : str
            The title of the label
        row : int
            The row value, from the top down
        column : int
            The column value from the top down
        row_span=1 : int
            The number of rows to span accross
        column_span=1 : int
            the number of columns to span accross
        padx=1 : int
            number of padding characters in the x direction
        pady=0 : int
            number of padding characters in the y direction
        min_val = 0 int
            min value of the slider
        max_val = 0 int
            max value of the slider
        step = 0 int
            step to incremento or decrement
        init_val = 0 int
            initial value of the slider

        Returns
        -------
        new_slider : Slider
            A reference to the created slider object.
        """

        id = 'Widget{}'.format(len(self._widgets.keys()))
        new_slider = py_cui.controls.slider.SliderWidget(id,
                                                         title,
                                                         self._grid,
                                                         row,
                                                         column,
                                                         row_span,
                                                         column_span,
                                                         padx,
                                                         pady,
                                                         self._logger,
                                                         min_val,
                                                         max_val,
                                                         step,
                                                         init_val)
        self._widgets[id] = new_slider
        self._logger.info('Adding widget {} w/ ID {} of type {}'
                           .format(title, id, str(type(new_slider))))
        return new_slider

    
    def get_element_at_position(self, x, y):
        """Returns containing widget for character position

        Parameters
        ----------
        x : int
            Horizontal character position
        y : int
            Vertical character position, top down

        Returns
        -------
        in_widget : UIElement
            Widget or popup that is within the position None if nothing
        """

        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]._contains_position(x, y):
                    return self.get_widgets()[widget_id]
        return None


    def _get_horizontal_neighbors(self, widget, direction):
        """Gets all horizontal (left, right) neighbor widgets

        Parameters
        ----------
        widget : py_cui.widgets.Widget
            The currently selected widget
        direction : py_cui.keys.KEY*
            must be an arrow key value

        Returns
        -------
        id_list : list[]
            A list of the neighbor widget ids
        """

        if not direction in py_cui.keys.ARROW_KEYS:
            return None

        _,          num_cols    = self._grid.get_dimensions()
        row_start,  col_start   = widget.get_grid_cell()
        row_span,   col_span    = widget.get_grid_cell_spans()
        id_list                 = []

        if direction == py_cui.keys.KEY_LEFT_ARROW:
            col_range_start = 0
            col_range_stop = col_start
        else:
            col_range_start = col_start + col_span
            col_range_stop = num_cols

        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 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))
        return id_list


    def _get_vertical_neighbors(self, widget, direction):
        """Gets all vertical (up, down) neighbor widgets

        Parameters
        ----------
        widget : py_cui.widgets.Widget
            The currently selected widget
        direction : py_cui.keys.KEY*
            must be an arrow key value

        Returns
        -------
        id_list : list[]
            A list of the neighbor widget ids
        """

        if not direction in py_cui.keys.ARROW_KEYS:
            return None

        num_rows,   _           = self._grid.get_dimensions()
        row_start,  col_start   = widget.get_grid_cell()
        row_span,   col_span    = widget.get_grid_cell_spans()
        id_list                 = []

        if direction == py_cui.keys.KEY_UP_ARROW:
            row_range_start = 0
            row_range_stop = row_start
        else:
            row_range_start = row_start + row_span
            row_range_stop = num_rows

        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 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))
        return id_list

    # CUI status functions. Used to switch between widgets, set the mode, and
    # identify neighbors for overview mode

    def _check_if_neighbor_exists(self, direction):
        """Function that checks if widget has neighbor in specified cell.

        Used for navigating CUI, as arrow keys find the immediate neighbor

        Parameters
        ----------
        direction : py_cui.keys.KEY_*
            The direction in which to search

        Returns
        -------
        widget_id : str
            The widget neighbor ID if found, None otherwise
        """

        start_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:
            return None

        # We select the best match to jump to (first neighbor)
        return neighbors[0]


    def get_selected_widget(self):
        """Function that gets currently selected widget

        Returns
        -------
        selected_widget : py_cui.widgets.Widget
            Reference to currently selected widget object
        """

        if self._selected_widget is not None and self._selected_widget in self.get_widgets().keys():
            return self.get_widgets()[self._selected_widget]
        else:
            self._logger.warn('Selected widget ID is None or invalid')
            return None


    def set_selected_widget(self, widget_id):
        """Function that sets the selected widget for the CUI

        Parameters
        ----------
        widget_id : str
            the id of the widget to select
        """

        if widget_id in self.get_widgets().keys():
            self._logger.info('Setting selected widget to ID {}'.format(widget_id))
            self._selected_widget = widget_id
        else:
            self._logger.warn('Widget w/ ID {} does not exist among current widgets.'.format(widget_id))


    def lose_focus(self):
        """Function that forces py_cui out of focus mode.

        After popup is called, focus is lost
        """

        if self._in_focused_mode:
            self._in_focused_mode = False
            self.status_bar.set_text(self._init_status_bar_text)
            self.get_widgets()[self._selected_widget].set_selected(False)
        else:
            self._logger.info('lose_focus: Not currently in focus mode')


    def move_focus(self, widget, auto_press_buttons=True):
        """Moves focus mode to different widget

        Parameters
        ----------
        widget : Widget
            The widget object we want to move focus to.
        """

        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):
            widget.command()
            self._logger.info('Moved focus to button {} - ran autofocus command'.format(widget.get_title()))
        elif self._auto_focus_buttons and isinstance(widget, py_cui.widgets.Button):
            self.status_bar.set_text(self._init_status_bar_text)
        else:
            widget.set_selected(True)
            self._in_focused_mode = True
            self.status_bar.set_text(widget.get_help_text())
        self._logger.info('Moved focus to widget {}'.format(widget.get_title()))


    def _cycle_widgets(self, reverse=False):
        """Function that is fired if cycle key is pressed to move to next widget

        Parameters
        ----------
        reverse : bool
            Default false. If true, cycle widgets in reverse order.
        """

        num_widgets = len(self.get_widgets().keys())
        current_widget_num = int(self._selected_widget.split('Widget')[1])

        if not reverse:
            next_widget_num = current_widget_num + 1
            if next_widget_num == num_widgets:
                next_widget_num = 0
            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
            cycle_key = self._reverse_cycle_key

        current_widget_id = 'Widget{}'.format(current_widget_num)
        next_widget_id = 'Widget{}'.format(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)


    def add_key_command(self, key, command):
        """Function that adds a keybinding to the CUI when in overview mode

        Parameters
        ----------
        key : py_cui.keys.KEY_*
            The key bound to the command
        command : Function
            A no-arg or lambda function to fire on keypress
        """

        self._keybindings[key] = command

    # Popup functions. Used to display messages, warnings, and errors to the user.

    def show_message_popup(self, title, text):
        """Shows a message popup

        Parameters
        ----------
        title : str
            Message title
        text : str
            Message text
        """

        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()))


    def show_warning_popup(self, title, text):
        """Shows a warning popup

        Parameters
        ----------
        title : str
            Warning title
        text : str
            Warning text
        """

        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()))


    def show_error_popup(self, title, text):
        """Shows an error popup

        Parameters
        ----------
        title : str
            Error title
        text : str
            Error text
        """

        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()))


    def show_yes_no_popup(self, title, command):
        """Shows a yes/no popup.

        The 'command' parameter must be a function with a single boolean parameter

        Parameters
        ----------
        title : str
            Message title
        command : function
            A function taking in a single boolean parameter. Will be fired with True if yes selected, false otherwise
        """

        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()))


    def show_text_box_popup(self, title, command, password=False):
        """Shows a textbox popup.

        The 'command' parameter must be a function with a single string parameter

        Parameters
        ----------
        title : str
            Message title
        command : Function
            A function with a single string parameter, fired with contents of textbox when enter key pressed
        password=False : bool
            If true, write characters as '*'
        """

        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()))


    def show_menu_popup(self, title, menu_items, command, run_command_if_none=False):
        """Shows a menu popup.

        The 'command' parameter must be a function with a single string parameter

        Parameters
        ----------
        title : str
            menu title
        menu_items : list of str
            A list of menu items
        command : Function
            A function taking in a single string argument. Fired with selected menu item when ENTER pressed.
        run_command_if_none=False : bool
            If True, will run command passing in None if no menu item selected.
        """

        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()))


    def show_loading_icon_popup(self, title, message, callback=None):
        """Shows a loading icon popup

        Parameters
        ----------
        title : str
            Message title
        message : str
            Message text. Will show as '$message...'
        callback=None : Function
            If not none, fired after loading is completed. Must be a no-arg function
        """

        if callback is not None:
            self._post_loading_callback = callback

        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()))


    def show_loading_bar_popup(self, title, num_items, callback=None):
        """Shows loading bar popup.

        Use 'increment_loading_bar' to show progress

        Parameters
        ----------
        title : str
            Message title
        num_items : int
            Number of items to iterate through for loading
        callback=None : Function
            If not none, fired after loading is completed. Must be a no-arg function
        """

        if callback is not None:
            self._post_loading_callback = callback

        color         = WHITE_ON_BLACK
        self._loading = True
        self._popup   = py_cui.popups.LoadingBarPopup(self, title, num_items, color, self._renderer, self._logger)
        self._logger.info('Opened {} popup with title {}'.format(str(type(self._popup)), self._popup.get_title()))


    def show_form_popup(self, title, fields, passwd_fields=[], required=[], callback=None):
        """Shows form popup.

        Used for inputting several fields worth of values

        Parameters
        ---------
        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
        """

        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)


    def show_filedialog_popup(self, popup_type='openfile', initial_dir='.', callback=None, ascii_icons=True, limit_extensions=[]):
        """Shows form popup.

        Used for inputting several fields worth of values

        Paramters
        ---------
        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
        """

        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)


    def increment_loading_bar(self):
        """Increments progress bar if loading bar popup is open
        """

        if self._popup is not None:
            self._popup._increment_counter()
        else:
            self._logger.warn('No popup is currently opened.')


    def stop_loading_popup(self):
        """Leaves loading state, and closes popup.

        Must be called by user to escape loading.
        """

        self._loading = False
        self.close_popup()
        self._logger.info('Stopping open loading popup')


    def close_popup(self):
        """Closes the popup, and resets focus
        """

        self.lose_focus()
        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

        Parameters
        ----------
        height : int
            Window height in terminal characters
        width : int
            Window width in terminal characters
        """

        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()
        if self._popup is not None:
            self._popup.update_height_width()


    def get_absolute_size(self):
        """Returns dimensions of CUI

        Returns
        -------
        height, width : int
            The dimensions of drawable CUI space in characters
        """

        return self._height, self._width

    # Draw Functions. Function for drawing widgets, status bars, and popups

    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()

        # We draw the selected widget last to support cursor location.
        if self._selected_widget is not None:
            self.get_widgets()[self._selected_widget]._draw()

        self._logger.info('Drew widgets')


    def _draw_status_bars(self, stdscr, height, width):
        """Draws status bar and title bar

        Parameters
        ----------
        stdscr : curses Standard cursor
            The cursor used to draw the status bar
        height : int
            Window height in terminal characters
        width : int
            Window width in terminal characters
        """

        if self.status_bar is not None:
            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:
            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()))


    def _display_window_warning(self, stdscr, error_info):
        """Function that prints some basic error info if there is an error with the CUI

        Parameters
        ----------
        stdscr : curses Standard cursor
            The cursor used to draw the warning
        error_info : str
            The information regarding the error.
        """

        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(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))


    def _handle_key_presses(self, key_pressed):
        """Function that handles all main loop key presses.

        Parameters
        ----------
        key_pressed : py_cui.keys.KEY_*
            The key being pressed
        """

        # Selected widget represents which widget is being hovered over, though not necessarily in focus mode
        if self._selected_widget is None:
            return

        selected_widget = self.get_widgets()[self._selected_widget]

        # If we are in focus mode, the widget has all of the control of the keyboard except
        # for the escape key, which exits focus mode.
        if self._in_focused_mode and self._popup is None:
            if key_pressed == py_cui.keys.KEY_ESCAPE:
                self.status_bar.set_text(self._init_status_bar_text)
                self._in_focused_mode = False
                selected_widget.set_selected(False)
                self._logger.info('Exiting focus mode on widget {}'.format(selected_widget.get_title()))
            else:
                # widget handles remaining py_cui.keys
                self._logger.info('Widget {} handling {} key'.format(selected_widget.get_title(), key_pressed))
                selected_widget._handle_key_press(key_pressed)

        # Otherwise, barring a popup, we are in overview mode, meaning that arrow py_cui.keys move between widgets, and Enter key starts focus mode
        elif self._popup is None:
            if key_pressed == py_cui.keys.KEY_ENTER and self._selected_widget is not None and selected_widget.is_selectable():
                self.move_focus(selected_widget)

            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__))
                    command()

            # If not in focus mode, use the arrow py_cui.keys to move around the selectable widgets.
            neighbor = None
            if key_pressed in py_cui.keys.ARROW_KEYS:
                neighbor = self._check_if_neighbor_exists(key_pressed)
            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()))

        # 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._popup._handle_key_press(key_pressed)


    def _draw(self, stdscr):
        """Main CUI draw loop called by start()

        Parameters
        ----------
        stdscr : curses Standard screen
            The screen buffer used for drawing CUI elements
        """

        self._stdscr = stdscr
        key_pressed = 0

        # Clear and refresh the screen for a blank canvas
        stdscr.clear()
        stdscr.refresh()
        curses.mousemask(curses.ALL_MOUSE_EVENTS)
        # stdscr.nodelay(False)
        #stdscr.keypad(True)

        # Initialization functions. Generates colors and renderer
        self._initialize_colors()
        self._initialize_widget_renderer()
        
        # If user specified a refresh timeout, apply it here
        if self._refresh_timeout > 0:
            self._stdscr.timeout(self._refresh_timeout)

        # If user sets non-default border characters, update them here
        if self._border_characters is not None:
            self._renderer._set_border_renderer_chars(self._border_characters)

        # Loop where key_pressed is the last character pressed. Wait for exit key while no popup or focus mode
        while key_pressed != self._exit_key or self._in_focused_mode or self._popup is not None:

            try:
                # If we call stop, we want to break out of the main draw loop
                if self._stopped:
                    break

                # 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
                # through a separate thread.
                if self._on_draw_update_func is not None:
                    self._on_draw_update_func()

                # 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))
                    try:
                        self._refresh_height_width(height, width)
                    except py_cui.errors.PyCUIOutOfBoundsError as e:
                        self._logger.info('Resized terminal too small')
                        self._display_window_warning(stdscr, str(e))

                # Here we handle mouse click events globally, or pass them to the UI element to handle
                elif key_pressed == curses.KEY_MOUSE:
                    self._logger.info('Detected mouse click')
                    _, x, y, _, _ = 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)
                    # 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)

                # 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._post_loading_callback()
                    self._post_loading_callback = None

                # Handle widget cycling
                if key_pressed == self._forward_cycle_key:
                    self._cycle_widgets()
                elif key_pressed == self._reverse_cycle_key:
                    self._cycle_widgets(reverse=True)

                # Handle keypresses
                self._handle_key_presses(key_pressed)

                try:
                    # Draw status/title bar, and all widgets. Selected widget will be bolded.
                    self._draw_status_bars(stdscr, height, width)
                    self._draw_widgets()
                    # draw the popup if required
                    if self._popup is not None:
                        self._popup._draw()
                except curses.error as e:
                    self._logger.error('Curses error while drawing TUI')
                    self._display_window_warning(stdscr, str(e))
                except py_cui.errors.PyCUIOutOfBoundsError as e:
                    self._logger.error('Resized terminal too small')
                    self._display_window_warning(stdscr, str(e))

                # Refresh the screen
                stdscr.refresh()

                # Wait for next input
                if self._loading or self._post_loading_callback is not None:
                    # When loading, refresh screen every quarter second
                    time.sleep(0.25)
                    # Need to reset key_pressed, because otherwise the previously pressed key will be used.
                    key_pressed = 0
                elif self._stopped:
                    key_pressed = self._exit_key
                else:
                    self._logger.info('Waiting for next keypress')
                    key_pressed = stdscr.getch()

            except KeyboardInterrupt:
                self._logger.info('Detect Keyboard Interrupt, Exiting...')
                self._stopped = True


        stdscr.erase()
        stdscr.refresh()
        curses.endwin()
        if self._on_stop is not None:
            self._logger.info('Firing onstop function {}'.format(self._on_stop.__name__))
            self._on_stop()


    def __format__(self, fmt):
        """Override of base format function. Prints list of current widgets.

        Parameters
        ----------
        fmt : Format
            The format to override
        """

        out = ''
        for widget in self.get_widgets().keys():
            out += '{}\n'.format(self.get_widgets()[widget].get_title())
        return out

[((120, 8, 120, 47), 'os.environ.setdefault', 'os.environ.setdefault', ({(120, 30, 120, 40): '"""ESCDELAY"""', (120, 42, 120, 46): '"""25"""'}, {}), "('ESCDELAY', '25')", False, 'import os\n'), ((141, 25, 141, 80), 'py_cui.statusbar.StatusBar', 'py_cui.statusbar.StatusBar', ({(141, 52, 141, 63): 'self._title', (141, 65, 141, 79): 'BLACK_ON_WHITE'}, {}), '(self._title, BLACK_ON_WHITE)', False, 'import py_cui\n'), ((142, 24, 142, 65), 'py_cui.keys.get_char_from_ascii', 'py_cui.keys.get_char_from_ascii', ({(142, 56, 142, 64): 'exit_key'}, {}), '(exit_key)', False, 'import py_cui\n'), ((146, 26, 147, 68), 'py_cui.statusbar.StatusBar', 'py_cui.statusbar.StatusBar', ({(146, 53, 146, 79): 'self._init_status_bar_text', (147, 53, 147, 67): 'BLACK_ON_WHITE'}, {}), '(self._init_status_bar_text, BLACK_ON_WHITE)', False, 'import py_cui\n'), ((150, 23, 151, 69), 'py_cui.debug._initialize_logger', 'py_cui.debug._initialize_logger', (), '', False, 'import py_cui\n'), ((154, 38, 154, 115), 'py_cui.grid.Grid', 'py_cui.grid.Grid', ({(154, 55, 154, 63): 'num_rows', (154, 65, 154, 73): 'num_cols', (154, 75, 154, 87): 'self._height', (154, 89, 154, 100): 'self._width', (154, 102, 154, 114): 'self._logger'}, {}), '(num_rows, num_cols, self._height, self._width, self._logger)', False, 'import py_cui\n'), ((303, 15, 304, 88), 'py_cui.widget_set.WidgetSet', 'py_cui.widget_set.WidgetSet', (), '', False, 'import py_cui\n'), ((318, 8, 318, 34), 'curses.wrapper', 'curses.wrapper', ({(318, 23, 318, 33): 'self._draw'}, {}), '(self._draw)', False, 'import curses\n'), ((374, 8, 374, 28), 'curses.start_color', 'curses.start_color', ({}, {}), '()', False, 'import curses\n'), ((375, 8, 375, 55), 'curses.init_color', 'curses.init_color', ({(375, 26, 375, 43): 'curses.COLOR_BLUE', (375, 45, 375, 46): '(0)', (375, 48, 375, 49): '(0)', (375, 51, 375, 54): '(500)'}, {}), '(curses.COLOR_BLUE, 0, 0, 500)', False, 'import curses\n'), ((376, 26, 376, 57), 'py_cui.colors._COLOR_MAP.keys', 'py_cui.colors._COLOR_MAP.keys', ({}, {}), '()', False, 'import py_cui\n'), ((474, 30, 483, 62), 'py_cui.widgets.ScrollMenu', 'py_cui.widgets.ScrollMenu', ({(474, 56, 474, 58): 'id', (475, 49, 475, 54): 'title', (476, 49, 476, 59): 'self._grid', (477, 49, 477, 52): 'row', (478, 49, 478, 55): 'column', (479, 49, 479, 57): 'row_span', (480, 49, 480, 60): 'column_span', (481, 49, 481, 53): 'padx', (482, 49, 482, 53): 'pady', (483, 49, 483, 61): 'self._logger'}, {}), '(id, title, self._grid, row, column, row_span,\n    column_span, padx, pady, self._logger)', False, 'import py_cui\n'), ((520, 30, 530, 64), 'py_cui.widgets.CheckBoxMenu', 'py_cui.widgets.CheckBoxMenu', ({(520, 58, 520, 60): 'id', (521, 51, 521, 56): 'title', (522, 51, 522, 61): 'self._grid', (523, 51, 523, 54): 'row', (524, 51, 524, 57): 'column', (525, 51, 525, 59): 'row_span', (526, 51, 526, 62): 'column_span', (527, 51, 527, 55): 'padx', (528, 51, 528, 55): 'pady', (529, 51, 529, 63): 'self._logger', (530, 51, 530, 63): 'checked_char'}, {}), '(id, title, self._grid, row, column, row_span,\n    column_span, padx, pady, self._logger, checked_char)', False, 'import py_cui\n'), ((569, 30, 578, 55), 'py_cui.widgets.TextBox', 'py_cui.widgets.TextBox', ({(569, 53, 569, 55): 'id', (570, 46, 570, 51): 'title', (571, 46, 571, 56): 'self._grid', (572, 46, 572, 49): 'row', (572, 51, 572, 57): 'column', (573, 46, 573, 54): 'row_span', (574, 46, 574, 57): 'column_span', (575, 46, 575, 50): 'padx', (575, 52, 575, 56): 'pady', (576, 46, 576, 58): 'self._logger', (577, 46, 577, 58): 'initial_text', (578, 46, 578, 54): 'password'}, {}), '(id, title, self._grid, row, column, row_span,\n    column_span, padx, pady, self._logger, initial_text, password)', False, 'import py_cui\n'), ((615, 30, 625, 67), 'py_cui.widgets.ScrollTextBlock', 'py_cui.widgets.ScrollTextBlock', ({(615, 61, 615, 63): 'id', (616, 54, 616, 59): 'title', (617, 54, 617, 64): 'self._grid', (618, 54, 618, 57): 'row', (619, 54, 619, 60): 'column', (620, 54, 620, 62): 'row_span', (621, 54, 621, 65): 'column_span', (622, 54, 622, 58): 'padx', (623, 54, 623, 58): 'pady', (624, 54, 624, 66): 'self._logger', (625, 54, 625, 66): 'initial_text'}, {}), '(id, title, self._grid, row, column, row_span,\n    column_span, padx, pady, self._logger, initial_text)', False, 'import py_cui\n'), ((660, 30, 669, 57), 'py_cui.widgets.Label', 'py_cui.widgets.Label', ({(660, 51, 660, 53): 'id', (661, 44, 661, 49): 'title', (662, 44, 662, 54): 'self._grid', (663, 44, 663, 47): 'row', (664, 44, 664, 50): 'column', (665, 44, 665, 52): 'row_span', (666, 44, 666, 55): 'column_span', (667, 44, 667, 48): 'padx', (668, 44, 668, 48): 'pady', (669, 44, 669, 56): 'self._logger'}, {}), '(id, title, self._grid, row, column, row_span,\n    column_span, padx, pady, self._logger)', False, 'import py_cui\n'), ((704, 30, 714, 62), 'py_cui.widgets.BlockLabel', 'py_cui.widgets.BlockLabel', ({(704, 56, 704, 58): 'id', (705, 49, 705, 54): 'title', (706, 49, 706, 59): 'self._grid', (707, 49, 707, 52): 'row', (708, 49, 708, 55): 'column', (709, 49, 709, 57): 'row_span', (710, 49, 710, 60): 'column_span', (711, 49, 711, 53): 'padx', (712, 49, 712, 53): 'pady', (713, 49, 713, 55): 'center', (714, 49, 714, 61): 'self._logger'}, {}), '(id, title, self._grid, row, column, row_span,\n    column_span, padx, pady, center, self._logger)', False, 'import py_cui\n'), ((749, 30, 759, 53), 'py_cui.widgets.Button', 'py_cui.widgets.Button', ({(749, 52, 749, 54): 'id', (750, 45, 750, 50): 'title', (751, 45, 751, 55): 'self._grid', (752, 45, 752, 48): 'row', (753, 45, 753, 51): 'column', (754, 45, 754, 53): 'row_span', (755, 45, 755, 56): 'column_span', (756, 45, 756, 49): 'padx', (757, 45, 757, 49): 'pady', (758, 45, 758, 57): 'self._logger', (759, 45, 759, 52): 'command'}, {}), '(id, title, self._grid, row, column, row_span,\n    column_span, padx, pady, self._logger, command)', False, 'import py_cui\n'), ((804, 21, 817, 66), 'py_cui.controls.slider.SliderWidget', 'py_cui.controls.slider.SliderWidget', ({(804, 57, 804, 59): 'id', (805, 57, 805, 62): 'title', (806, 57, 806, 67): 'self._grid', (807, 57, 807, 60): 'row', (808, 57, 808, 63): 'column', (809, 57, 809, 65): 'row_span', (810, 57, 810, 68): 'column_span', (811, 57, 811, 61): 'padx', (812, 57, 812, 61): 'pady', (813, 57, 813, 69): 'self._logger', (814, 57, 814, 64): 'min_val', (815, 57, 815, 64): 'max_val', (816, 57, 816, 61): 'step', (817, 57, 817, 65): 'init_val'}, {}), '(id, title, self._grid, row, column,\n    row_span, column_span, padx, pady, self._logger, min_val, max_val, step,\n    init_val)', False, 'import py_cui\n'), ((1108, 22, 1108, 104), 'py_cui.popups.MessagePopup', 'py_cui.popups.MessagePopup', ({(1108, 49, 1108, 53): 'self', (1108, 55, 1108, 60): 'title', (1108, 62, 1108, 66): 'text', (1108, 68, 1108, 73): 'color', (1108, 75, 1108, 89): 'self._renderer', (1108, 91, 1108, 103): 'self._logger'}, {}), '(self, title, text, color, self._renderer, self.\n    _logger)', False, 'import py_cui\n'), ((1124, 22, 1124, 119), 'py_cui.popups.MessagePopup', 'py_cui.popups.MessagePopup', ({(1124, 49, 1124, 53): 'self', (1124, 55, 1124, 75): "'WARNING - ' + title", (1124, 77, 1124, 81): 'text', (1124, 83, 1124, 88): 'color', (1124, 90, 1124, 104): 'self._renderer', (1124, 106, 1124, 118): 'self._logger'}, {}), "(self, 'WARNING - ' + title, text, color, self.\n    _renderer, self._logger)", False, 'import py_cui\n'), ((1140, 22, 1140, 117), 'py_cui.popups.MessagePopup', 'py_cui.popups.MessagePopup', ({(1140, 49, 1140, 53): 'self', (1140, 55, 1140, 73): "'ERROR - ' + title", (1140, 75, 1140, 79): 'text', (1140, 81, 1140, 86): 'color', (1140, 88, 1140, 102): 'self._renderer', (1140, 104, 1140, 116): 'self._logger'}, {}), "(self, 'ERROR - ' + title, text, color, self.\n    _renderer, self._logger)", False, 'import py_cui\n'), ((1158, 22, 1158, 140), 'py_cui.popups.YesNoPopup', 'py_cui.popups.YesNoPopup', ({(1158, 47, 1158, 51): 'self', (1158, 53, 1158, 70): "title + '- (y/n)'", (1158, 72, 1158, 93): '"""Yes - (y), No - (n)"""', (1158, 95, 1158, 100): 'color', (1158, 102, 1158, 109): 'command', (1158, 111, 1158, 125): 'self._renderer', (1158, 127, 1158, 139): 'self._logger'}, {}), "(self, title + '- (y/n)', 'Yes - (y), No - (n)',\n    color, command, self._renderer, self._logger)", False, 'import py_cui\n'), ((1178, 22, 1178, 117), 'py_cui.popups.TextBoxPopup', 'py_cui.popups.TextBoxPopup', ({(1178, 49, 1178, 53): 'self', (1178, 55, 1178, 60): 'title', (1178, 62, 1178, 67): 'color', (1178, 69, 1178, 76): 'command', (1178, 78, 1178, 92): 'self._renderer', (1178, 94, 1178, 102): 'password', (1178, 104, 1178, 116): 'self._logger'}, {}), '(self, title, color, command, self._renderer,\n    password, self._logger)', False, 'import py_cui\n'), ((1200, 22, 1200, 137), 'py_cui.popups.MenuPopup', 'py_cui.popups.MenuPopup', ({(1200, 46, 1200, 50): 'self', (1200, 52, 1200, 62): 'menu_items', (1200, 64, 1200, 69): 'title', (1200, 71, 1200, 76): 'color', (1200, 78, 1200, 85): 'command', (1200, 87, 1200, 101): 'self._renderer', (1200, 103, 1200, 115): 'self._logger', (1200, 117, 1200, 136): 'run_command_if_none'}, {}), '(self, menu_items, title, color, command, self.\n    _renderer, self._logger, run_command_if_none)', False, 'import py_cui\n'), ((1222, 24, 1222, 113), 'py_cui.popups.LoadingIconPopup', 'py_cui.popups.LoadingIconPopup', ({(1222, 55, 1222, 59): 'self', (1222, 61, 1222, 66): 'title', (1222, 68, 1222, 75): 'message', (1222, 77, 1222, 82): 'color', (1222, 84, 1222, 98): 'self._renderer', (1222, 100, 1222, 112): 'self._logger'}, {}), '(self, title, message, color, self._renderer,\n    self._logger)', False, 'import py_cui\n'), ((1246, 24, 1246, 114), 'py_cui.popups.LoadingBarPopup', 'py_cui.popups.LoadingBarPopup', ({(1246, 54, 1246, 58): 'self', (1246, 60, 1246, 65): 'title', (1246, 67, 1246, 76): 'num_items', (1246, 78, 1246, 83): 'color', (1246, 85, 1246, 99): 'self._renderer', (1246, 101, 1246, 113): 'self._logger'}, {}), '(self, title, num_items, color, self._renderer,\n    self._logger)', False, 'import py_cui\n'), ((1269, 22, 1269, 154), 'py_cui.dialogs.form.FormPopup', 'py_cui.dialogs.form.FormPopup', ({(1269, 52, 1269, 56): 'self', (1269, 58, 1269, 64): 'fields', (1269, 66, 1269, 79): 'passwd_fields', (1269, 81, 1269, 89): 'required', (1269, 91, 1269, 93): '{}', (1269, 95, 1269, 100): 'title', (1269, 102, 1269, 123): 'py_cui.WHITE_ON_BLACK', (1269, 125, 1269, 139): 'self._renderer', (1269, 141, 1269, 153): 'self._logger'}, {}), '(self, fields, passwd_fields, required, {},\n    title, py_cui.WHITE_ON_BLACK, self._renderer, self._logger)', False, 'import py_cui\n'), ((1293, 22, 1293, 188), 'py_cui.dialogs.filedialog.FileDialogPopup', 'py_cui.dialogs.filedialog.FileDialogPopup', ({(1293, 64, 1293, 68): 'self', (1293, 70, 1293, 78): 'callback', (1293, 80, 1293, 91): 'initial_dir', (1293, 93, 1293, 103): 'popup_type', (1293, 105, 1293, 116): 'ascii_icons', (1293, 118, 1293, 134): 'limit_extensions', (1293, 136, 1293, 157): 'py_cui.WHITE_ON_BLACK', (1293, 159, 1293, 173): 'self._renderer', (1293, 175, 1293, 187): 'self._logger'}, {}), '(self, callback, initial_dir,\n    popup_type, ascii_icons, limit_extensions, py_cui.WHITE_ON_BLACK, self.\n    _renderer, self._logger)', False, 'import py_cui\n'), ((1486, 8, 1486, 49), 'curses.mousemask', 'curses.mousemask', ({(1486, 25, 1486, 48): 'curses.ALL_MOUSE_EVENTS'}, {}), '(curses.ALL_MOUSE_EVENTS)', False, 'import curses\n'), ((1603, 8, 1603, 23), 'curses.endwin', 'curses.endwin', ({}, {}), '()', False, 'import curses\n'), ((127, 24, 127, 50), 'shutil.get_terminal_size', 'shutil.get_terminal_size', ({}, {}), '()', False, 'import shutil\n'), ((235, 12, 235, 107), 'py_cui.debug._enable_logging', 'py_cui.debug._enable_logging', (), '', False, 'import py_cui\n'), ((378, 12, 378, 60), 'curses.init_pair', 'curses.init_pair', ({(378, 29, 378, 39): 'color_pair', (378, 41, 378, 49): 'fg_color', (378, 51, 378, 59): 'bg_color'}, {}), '(color_pair, fg_color, bg_color)', False, 'import curses\n'), ((386, 29, 386, 87), 'py_cui.renderer.Renderer', 'py_cui.renderer.Renderer', ({(386, 54, 386, 58): 'self', (386, 60, 386, 72): 'self._stdscr', (386, 74, 386, 86): 'self._logger'}, {}), '(self, self._stdscr, self._logger)', False, 'import py_cui\n'), ((1409, 22, 1409, 53), 'curses.color_pair', 'curses.color_pair', ({(1409, 40, 1409, 52): 'RED_ON_BLACK'}, {}), '(RED_ON_BLACK)', False, 'import curses\n'), ((1413, 23, 1413, 54), 'curses.color_pair', 'curses.color_pair', ({(1413, 41, 1413, 53): 'RED_ON_BLACK'}, {}), '(RED_ON_BLACK)', False, 'import curses\n'), ((263, 32, 263, 58), 'shutil.get_terminal_size', 'shutil.get_terminal_size', ({}, {}), '()', False, 'import shutil\n'), ((1587, 20, 1587, 36), 'time.sleep', 'time.sleep', ({(1587, 31, 1587, 35): '(0.25)'}, {}), '(0.25)', False, 'import time\n'), ((1541, 36, 1541, 53), 'curses.getmouse', 'curses.getmouse', ({}, {}), '()', False, 'import curses\n')]