<filename>frontend/left_window.py<gh_stars>0
"""
Module that represents the left panel of the Skipper interactive terminal tool.

 window________________________
|  __________________________  |
| | bc_window (breadcrumbs)  | |
| |__________________________| |
|  __________________________  |
| | th_window (table headers)| |
| |__________________________| |
|  __________________________  |
| | table_window             | |
| |  ______________________  | |
| | |tr_window (table row) | | |
| | |______________________| | |
| |  ______________________  | |
| | |tr_window (table row) | | |
| | |______________________| | |
| | ...                      | |
| |                          | |
| |                          | |
| |                          | |
| |                          | |
| |__________________________| |
|______________________________|
"""

import sys, curses
from typing import List
import curses_helpers as chs
import search_bar as sb

this = sys.modules[__name__]	# used to reference module variables

mode = "app"
path_names = []			# path to display in breadcrumbs window
path_rtypes = []		# resource types of items in the path
path_uids = []			# uids of items in path
col_names = []			# column header names
col_widths = []			# width of each column
table = []				# rows of data to be displayed in table window (curses.pad)
table_uids = []			# uids for each resource in the table
has_children = []		# boolean (has children or not) for each resource in table
sev = []				# list of sev measures for each item in table
row_selector = 0		# which row of the table should be highlighted

bc_height = 4			# height of the breadcrumbs window
sb_height = 4			# height of the search bar window
th_height = 3			# height of the table headers window
tr_height = 3			# height of a table row window
table_height = 0		# height of the table window

x,y = 0,0				# absolute x,y of upper-left corner of left_window
width,height = 0,0		# width and height of left_window
end_x,end_y = 0,0		# absolute x,y of lower-right corner of left_window

bcx, bcy = 0,0			# relative x,y of upper-left corner of breadcrumbs window
sbx, sby = 0,0			# relative x,y of upper-left corner of search bar window
thx, thy = 0,0			# relative x,y of upper-left corner of table headers window
table_x, table_y = 0,0	# absolute x,y of upper-left corner of table window (curses.pad)

window = None			# _curses.window object that represents the left window
bc_window = None		# _curses.window object that represents the breadcrumbs window
sb_window = None		# _curses.window object that represents the search bar window
th_window = None		# _curses.window object that represents that table headers window
table_window = None		# _curses.pad object that represents that table window

table_start_y = 0		# starting row of table pad

LEFT_PADDING = 5


def init_win(stdscr, height: int, width: int, y: int, x: int) -> None:
	"""
	Initializes the left window and sets relevant position variables.

	:param (_curses.window) stdscr
	:param (int) height: Desired height of the left window.
	:param (int) width: Desired width of the left window.
	:param (int) y: Y-coordinate of upper-left corner of window.
	:param (int) x: X-coordinate of upper-left corner of window.
	:return: None
	"""

	this.window = curses.newwin(height, width, y, x)

	h,w = stdscr.getmaxyx()
	this.x, this.y = x,y 						# set x,y of upper-left corner of the left window
	this.width, this.height = width, height 	# set width, height of the left window
	this.end_x, this.end_y = x + width, h - 1	# set x,y of lower-right corner of the left window
	this.bcx, this.bcy = 0,0					# set x,y of upper-left corner of breadcrumbs window

	this.tr_text_width = this.width - this.LEFT_PADDING

def set_contents(mode: str,
					col_names: List[str],
					col_widths: List[int],
					table: List[ List[str] ],
					row_selector: int,
					start_y: int,
					path_names: List[str],
					path_rtypes: List[str],
				 	path_uids: List[str],
				 	table_uids: List[str],
				 	has_children: List[bool],
               		sev: List[int]) -> None:
	"""
	Sets relevant content variables based on given arguments.

	:param (str) mode
	:param (List[str]) col_names:
					Names of the table column headers.
	:param (List[int]) col_widths:
					Widths of the table columns.
	:param (List[List[str]]) table:
					Rows of information to display in the table.
	:param (int) row_selector:
					Index of which row should be highlighted.
	:param (int) start_y:
					Starting row of table pad
	:param (List[str]) path_names:
					For app and cluster mode. List of resource names to display in breadcrumb window.
	:param (List[str]) path_rtypes:
					For app and cluster mode. List of resource types corresponding to the above resource names.
	:param (List[str]) path_uids:
					For app and cluster mode. List of resource uids corresponding to the above resource names.
	:param (List[str]) table_uids:
					List of uids for resources in table
	:param (List[bool]) has_children:
					List of booleans for knowing if resource has children
	:return: None
	"""

	this.window = window
	this.mode = mode
	this.col_names = col_names
	this.col_widths = col_widths
	this.table = table
	this.row_selector = row_selector
	this.table_start_y = start_y
	this.path_names = path_names
	this.path_rtypes = path_rtypes
	this.path_uids = path_uids
	this.table_uids = table_uids
	this.has_children = has_children
	this.sev = sev

	if mode in ["app", "cluster"]:
		this.thx, this.thy = 0,this.bc_height	# relative x,y inside left_window
		this.table_x, this.table_y = 0, y + this.bc_height + this.th_height # absolute x,y
		this.table_height = this.height - this.bc_height - this.th_height
	elif mode == "query":
		this.thx, this.thy = 0, this.sb_height	# relative x,y inside left_window
		this.table_x, this.table_y = 0, y + this.sb_height + this.th_height	# absolute x,y
		this.table_height = this.height - this.sb_height - this.th_height
	else:
		this.thx, this.thy = 0,0	# relative x,y inside left_window
		this.table_x, this.table_y = 0, y + this.th_height # absolute x,y
		this.table_height = this.height - this.th_height

def draw_bc_window(bc_window) -> None:
	"""
	Draws this.path_names and this.path_rtypes in the given breadcrumb window.

	:param (_curses.window) bc_window: _curses.window object that represents the breadcrumb window
	:return: None
	"""
	# edge case: path and rtypes are of different lengths

	# returns left and right padding needed to center str2 under str1
	def padding(str1, str2):
		if len(str2) >= len(str1):
			return (0, 0)
		left_pad = (len(str1) - len(str2)) // 2
		right_pad = len(str1) - len(str2) - left_pad
		return (left_pad, right_pad)

	# edge case: name is shorter than resource type
	# solution: pad path names to always be longer than their corresponding type
	padded_path_names = []
	for name,rtype in zip(this.path_names, this.path_rtypes):
		if len(rtype) > len(name):
			diff = len(rtype) - len(name) 
			pad = diff // 2 + diff % 2
			padded_path_names.append(pad * " " + name + pad * " ")
		else:
			padded_path_names.append(name)
	
	# create full path string
	path_str = "/ " + " / ".join(padded_path_names)
	if len(padded_path_names) > 0:
		path_str += " /"

	# create full type string
	type_str = "  "
	for name,rtype in zip(padded_path_names, this.path_rtypes):
		lpad, rpad = padding(name, rtype)
		type_str += lpad * " " + rtype + rpad * " "
		type_str += "   "

	# edge case: path is longer than bc window width
	# solution: front-truncate the strings, if necessary
	height, width = bc_window.getmaxyx()
	max_len = width - 2 * LEFT_PADDING
	if len(path_str) > max_len:
		chop_idx = -1 * max_len
		if path_str[chop_idx:].count("/") > 1:
			while path_str[chop_idx] != "/":
				chop_idx += 1
		path_str = "..." + path_str[chop_idx:]
		type_str = "   " + type_str[chop_idx:]

	bc_window.addstr(1, this.LEFT_PADDING, path_str)
	bc_window.addstr(2, this.LEFT_PADDING, type_str)

def draw_tr_window(tr_window, row: List[str], grey_out=False, sev=None) -> None:
	"""
	Formats and draws row in the given window.

	:param (_curses.window)	tr_window: _curses.window object that represents the table row
	:return: None
	"""

	# edge case: start positions are too close together for the col_names
	# edge case: start positions are too large for width of window
	# edge case: col_names and start_postions have different lengths

	if len(row) == 0 or len(row) != len(this.col_widths):
		print("Please check that you are passing in valid arguments to the draw_tr_window function.")
		return

	# pads and truncates the given string, as necessary, to fit col_width
	def fmt_str(string, col_width):
		if len(string) > col_width - 1:
			return string[:col_width - 4] + "... "
		return string + " " * (col_width - len(string))

	# generate a python format string based on col widths (this.col_widths)
	row_str = ""
	status_start = None
	for i, (entry, width) in enumerate(zip(row, this.col_widths)):
		if i == 5:
			status_start = len(row_str)
		row_str += fmt_str(entry, width)

	# draw the string inside the table row window
	if curses.can_change_color() and grey_out:
		tr_window.addstr(1, this.LEFT_PADDING, row_str, curses.color_pair(2))
	else:
		# color status part of string
		if status_start is not None and sev is not None:
			tr_window.addstr(1, this.LEFT_PADDING, row_str[:status_start])
			if sev == "0":
				tr_window.addstr(1, this.LEFT_PADDING + status_start, row_str[status_start:], curses.color_pair(4))
			elif sev == "1":
				tr_window.addstr(1, this.LEFT_PADDING + status_start, row_str[status_start:], curses.color_pair(5))
		else:
			tr_window.addstr(1, this.LEFT_PADDING, row_str)

def draw_table_window(table_window) -> None:
	"""
	Draws this.table inside the given curses.pad window.

	:param (_curses.pad) table_window: _curses.pad object that represents the table.
	:return: None
	"""

	# create a (derived) window for each row in this.table
	yloc = 0
	row_windows = []
	for row in this.table:
		tr_window = table_window.derwin(this.tr_height,this.width, yloc,0)
		row_windows.append( (row, tr_window) )
		yloc += this.tr_height

	# Draw the row windows
	for i, (entries, rowwin) in enumerate(row_windows):
		draw_tr_window(rowwin, entries, grey_out=(not this.has_children[i] and entries[0] != 'Pod'), sev=this.sev[i])

	# Highlight the row at index this.row_selector
	# if there are any rows
	if len(table) > 0:
		chs.highlight_window(row_windows[this.row_selector][1], grey=(not this.has_children[this.row_selector] and row_windows[this.row_selector][0][0] != 'Pod'))

def draw() -> None:
	"""
	Populates this.window with content given by the most recent call to set_contents(...)
	"""

	this.window.erase()

	# initialize and draw breadcrumbs derived window
	# only if Skipper is in app or cluster mode
	if this.mode == "app" or this.mode == "cluster":
		bc_window = window.derwin(this.bc_height,this.width, this.bcy,this.bcx)	# nlines, ncols, rel_y, rel_x
		draw_bc_window(bc_window)
		bc_window.refresh()

	# initialize and draw table header window
	th_window = window.derwin(this.th_height,this.width, this.thy,this.thx)	# nlines, ncols, rel_y, rel_x
	draw_tr_window(th_window, this.col_names)
	th_window.refresh()

	# initialize and draw table window (of type _curses.pad)
	pad_height = max(this.table_height, this.tr_height * len(this.table))
	table_window = curses.newpad( pad_height, this.width )	# nlines, ncols, start_y, start_x
	draw_table_window(table_window)

	# scroll down if we are at the bottom of the page
	if this.row_selector * this.tr_height >= this.table_start_y + this.table_height - this.tr_height:
		this.table_start_y += this.tr_height
	
	# scroll up if we are at the top of the page
	elif this.row_selector * this.tr_height <= this.table_start_y - this.tr_height:
		this.table_start_y -= this.tr_height
	
	table_window.refresh(this.table_start_y,0, this.table_y,this.table_x, this.end_y - this.end_y % this.tr_height,this.end_x)

	# draw search bar last so cursor stays in the bar
	if this.mode == "query":
		this.sb_window = window.derwin(this.sb_height,this.width, this.sby,this.sbx)	# nlines, ncols, rel_y, rel_x
		sb.set_window(this.sb_window)
		sb.draw()

def move_up():
	"""
	Decrements this.row_selector and redraws window, if necessary
	:return: (int) updated row_selector, or "empty" if empty table
	"""
	if len(this.table) == 0:
		return "empty"

	if this.row_selector > 0:
		this.row_selector -= 1
		this.draw()
	return this.table_uids[this.row_selector]


def move_down():
	"""
	Increments this.row_selector and redraws window, if necessary
	:return: (int) updated row_selector, or "empty" if empty table
	"""
	if len(this.table) == 0:
		return "empty"
		
	if this.row_selector < len(table)-1:
		this.row_selector += 1
		this.draw()
	return this.table_uids[this.row_selector]

def get_selected_row():
	"""
	Get selected row in table
	:return:
	"""
	return this.table[this.row_selector]

def get_column_widths(multiplier_list):
	"""
	Takes column width %s and returns list of corresponding col width raw values
	:param (List[float]) multiplier_list: the proportions that the total width should be divided into
	:return: (List[int]) the calculated widths of each column
	"""
	col_width_list = []
	for multiplier in multiplier_list:
		col_width_list.append(int(multiplier*this.tr_text_width))
	return col_width_list
<filename>frontend/left_window.py<gh_stars>0
"""
Module that represents the left panel of the Skipper interactive terminal tool.

 window________________________
|  __________________________  |
| | bc_window (breadcrumbs)  | |
| |__________________________| |
|  __________________________  |
| | th_window (table headers)| |
| |__________________________| |
|  __________________________  |
| | table_window             | |
| |  ______________________  | |
| | |tr_window (table row) | | |
| | |______________________| | |
| |  ______________________  | |
| | |tr_window (table row) | | |
| | |______________________| | |
| | ...                      | |
| |                          | |
| |                          | |
| |                          | |
| |                          | |
| |__________________________| |
|______________________________|
"""

import sys, curses
from typing import List
import curses_helpers as chs
import search_bar as sb

this = sys.modules[__name__]	# used to reference module variables

mode = "app"
path_names = []			# path to display in breadcrumbs window
path_rtypes = []		# resource types of items in the path
path_uids = []			# uids of items in path
col_names = []			# column header names
col_widths = []			# width of each column
table = []				# rows of data to be displayed in table window (curses.pad)
table_uids = []			# uids for each resource in the table
has_children = []		# boolean (has children or not) for each resource in table
sev = []				# list of sev measures for each item in table
row_selector = 0		# which row of the table should be highlighted

bc_height = 4			# height of the breadcrumbs window
sb_height = 4			# height of the search bar window
th_height = 3			# height of the table headers window
tr_height = 3			# height of a table row window
table_height = 0		# height of the table window

x,y = 0,0				# absolute x,y of upper-left corner of left_window
width,height = 0,0		# width and height of left_window
end_x,end_y = 0,0		# absolute x,y of lower-right corner of left_window

bcx, bcy = 0,0			# relative x,y of upper-left corner of breadcrumbs window
sbx, sby = 0,0			# relative x,y of upper-left corner of search bar window
thx, thy = 0,0			# relative x,y of upper-left corner of table headers window
table_x, table_y = 0,0	# absolute x,y of upper-left corner of table window (curses.pad)

window = None			# _curses.window object that represents the left window
bc_window = None		# _curses.window object that represents the breadcrumbs window
sb_window = None		# _curses.window object that represents the search bar window
th_window = None		# _curses.window object that represents that table headers window
table_window = None		# _curses.pad object that represents that table window

table_start_y = 0		# starting row of table pad

LEFT_PADDING = 5


def init_win(stdscr, height: int, width: int, y: int, x: int) -> None:
	"""
	Initializes the left window and sets relevant position variables.

	:param (_curses.window) stdscr
	:param (int) height: Desired height of the left window.
	:param (int) width: Desired width of the left window.
	:param (int) y: Y-coordinate of upper-left corner of window.
	:param (int) x: X-coordinate of upper-left corner of window.
	:return: None
	"""

	this.window = curses.newwin(height, width, y, x)

	h,w = stdscr.getmaxyx()
	this.x, this.y = x,y 						# set x,y of upper-left corner of the left window
	this.width, this.height = width, height 	# set width, height of the left window
	this.end_x, this.end_y = x + width, h - 1	# set x,y of lower-right corner of the left window
	this.bcx, this.bcy = 0,0					# set x,y of upper-left corner of breadcrumbs window

	this.tr_text_width = this.width - this.LEFT_PADDING

def set_contents(mode: str,
					col_names: List[str],
					col_widths: List[int],
					table: List[ List[str] ],
					row_selector: int,
					start_y: int,
					path_names: List[str],
					path_rtypes: List[str],
				 	path_uids: List[str],
				 	table_uids: List[str],
				 	has_children: List[bool],
               		sev: List[int]) -> None:
	"""
	Sets relevant content variables based on given arguments.

	:param (str) mode
	:param (List[str]) col_names:
					Names of the table column headers.
	:param (List[int]) col_widths:
					Widths of the table columns.
	:param (List[List[str]]) table:
					Rows of information to display in the table.
	:param (int) row_selector:
					Index of which row should be highlighted.
	:param (int) start_y:
					Starting row of table pad
	:param (List[str]) path_names:
					For app and cluster mode. List of resource names to display in breadcrumb window.
	:param (List[str]) path_rtypes:
					For app and cluster mode. List of resource types corresponding to the above resource names.
	:param (List[str]) path_uids:
					For app and cluster mode. List of resource uids corresponding to the above resource names.
	:param (List[str]) table_uids:
					List of uids for resources in table
	:param (List[bool]) has_children:
					List of booleans for knowing if resource has children
	:return: None
	"""

	this.window = window
	this.mode = mode
	this.col_names = col_names
	this.col_widths = col_widths
	this.table = table
	this.row_selector = row_selector
	this.table_start_y = start_y
	this.path_names = path_names
	this.path_rtypes = path_rtypes
	this.path_uids = path_uids
	this.table_uids = table_uids
	this.has_children = has_children
	this.sev = sev

	if mode in ["app", "cluster"]:
		this.thx, this.thy = 0,this.bc_height	# relative x,y inside left_window
		this.table_x, this.table_y = 0, y + this.bc_height + this.th_height # absolute x,y
		this.table_height = this.height - this.bc_height - this.th_height
	elif mode == "query":
		this.thx, this.thy = 0, this.sb_height	# relative x,y inside left_window
		this.table_x, this.table_y = 0, y + this.sb_height + this.th_height	# absolute x,y
		this.table_height = this.height - this.sb_height - this.th_height
	else:
		this.thx, this.thy = 0,0	# relative x,y inside left_window
		this.table_x, this.table_y = 0, y + this.th_height # absolute x,y
		this.table_height = this.height - this.th_height

def draw_bc_window(bc_window) -> None:
	"""
	Draws this.path_names and this.path_rtypes in the given breadcrumb window.

	:param (_curses.window) bc_window: _curses.window object that represents the breadcrumb window
	:return: None
	"""
	# edge case: path and rtypes are of different lengths

	# returns left and right padding needed to center str2 under str1
	def padding(str1, str2):
		if len(str2) >= len(str1):
			return (0, 0)
		left_pad = (len(str1) - len(str2)) // 2
		right_pad = len(str1) - len(str2) - left_pad
		return (left_pad, right_pad)

	# edge case: name is shorter than resource type
	# solution: pad path names to always be longer than their corresponding type
	padded_path_names = []
	for name,rtype in zip(this.path_names, this.path_rtypes):
		if len(rtype) > len(name):
			diff = len(rtype) - len(name) 
			pad = diff // 2 + diff % 2
			padded_path_names.append(pad * " " + name + pad * " ")
		else:
			padded_path_names.append(name)
	
	# create full path string
	path_str = "/ " + " / ".join(padded_path_names)
	if len(padded_path_names) > 0:
		path_str += " /"

	# create full type string
	type_str = "  "
	for name,rtype in zip(padded_path_names, this.path_rtypes):
		lpad, rpad = padding(name, rtype)
		type_str += lpad * " " + rtype + rpad * " "
		type_str += "   "

	# edge case: path is longer than bc window width
	# solution: front-truncate the strings, if necessary
	height, width = bc_window.getmaxyx()
	max_len = width - 2 * LEFT_PADDING
	if len(path_str) > max_len:
		chop_idx = -1 * max_len
		if path_str[chop_idx:].count("/") > 1:
			while path_str[chop_idx] != "/":
				chop_idx += 1
		path_str = "..." + path_str[chop_idx:]
		type_str = "   " + type_str[chop_idx:]

	bc_window.addstr(1, this.LEFT_PADDING, path_str)
	bc_window.addstr(2, this.LEFT_PADDING, type_str)

def draw_tr_window(tr_window, row: List[str], grey_out=False, sev=None) -> None:
	"""
	Formats and draws row in the given window.

	:param (_curses.window)	tr_window: _curses.window object that represents the table row
	:return: None
	"""

	# edge case: start positions are too close together for the col_names
	# edge case: start positions are too large for width of window
	# edge case: col_names and start_postions have different lengths

	if len(row) == 0 or len(row) != len(this.col_widths):
		print("Please check that you are passing in valid arguments to the draw_tr_window function.")
		return

	# pads and truncates the given string, as necessary, to fit col_width
	def fmt_str(string, col_width):
		if len(string) > col_width - 1:
			return string[:col_width - 4] + "... "
		return string + " " * (col_width - len(string))

	# generate a python format string based on col widths (this.col_widths)
	row_str = ""
	status_start = None
	for i, (entry, width) in enumerate(zip(row, this.col_widths)):
		if i == 5:
			status_start = len(row_str)
		row_str += fmt_str(entry, width)

	# draw the string inside the table row window
	if curses.can_change_color() and grey_out:
		tr_window.addstr(1, this.LEFT_PADDING, row_str, curses.color_pair(2))
	else:
		# color status part of string
		if status_start is not None and sev is not None:
			tr_window.addstr(1, this.LEFT_PADDING, row_str[:status_start])
			if sev == "0":
				tr_window.addstr(1, this.LEFT_PADDING + status_start, row_str[status_start:], curses.color_pair(4))
			elif sev == "1":
				tr_window.addstr(1, this.LEFT_PADDING + status_start, row_str[status_start:], curses.color_pair(5))
		else:
			tr_window.addstr(1, this.LEFT_PADDING, row_str)

def draw_table_window(table_window) -> None:
	"""
	Draws this.table inside the given curses.pad window.

	:param (_curses.pad) table_window: _curses.pad object that represents the table.
	:return: None
	"""

	# create a (derived) window for each row in this.table
	yloc = 0
	row_windows = []
	for row in this.table:
		tr_window = table_window.derwin(this.tr_height,this.width, yloc,0)
		row_windows.append( (row, tr_window) )
		yloc += this.tr_height

	# Draw the row windows
	for i, (entries, rowwin) in enumerate(row_windows):
		draw_tr_window(rowwin, entries, grey_out=(not this.has_children[i] and entries[0] != 'Pod'), sev=this.sev[i])

	# Highlight the row at index this.row_selector
	# if there are any rows
	if len(table) > 0:
		chs.highlight_window(row_windows[this.row_selector][1], grey=(not this.has_children[this.row_selector] and row_windows[this.row_selector][0][0] != 'Pod'))

def draw() -> None:
	"""
	Populates this.window with content given by the most recent call to set_contents(...)
	"""

	this.window.erase()

	# initialize and draw breadcrumbs derived window
	# only if Skipper is in app or cluster mode
	if this.mode == "app" or this.mode == "cluster":
		bc_window = window.derwin(this.bc_height,this.width, this.bcy,this.bcx)	# nlines, ncols, rel_y, rel_x
		draw_bc_window(bc_window)
		bc_window.refresh()

	# initialize and draw table header window
	th_window = window.derwin(this.th_height,this.width, this.thy,this.thx)	# nlines, ncols, rel_y, rel_x
	draw_tr_window(th_window, this.col_names)
	th_window.refresh()

	# initialize and draw table window (of type _curses.pad)
	pad_height = max(this.table_height, this.tr_height * len(this.table))
	table_window = curses.newpad( pad_height, this.width )	# nlines, ncols, start_y, start_x
	draw_table_window(table_window)

	# scroll down if we are at the bottom of the page
	if this.row_selector * this.tr_height >= this.table_start_y + this.table_height - this.tr_height:
		this.table_start_y += this.tr_height
	
	# scroll up if we are at the top of the page
	elif this.row_selector * this.tr_height <= this.table_start_y - this.tr_height:
		this.table_start_y -= this.tr_height
	
	table_window.refresh(this.table_start_y,0, this.table_y,this.table_x, this.end_y - this.end_y % this.tr_height,this.end_x)

	# draw search bar last so cursor stays in the bar
	if this.mode == "query":
		this.sb_window = window.derwin(this.sb_height,this.width, this.sby,this.sbx)	# nlines, ncols, rel_y, rel_x
		sb.set_window(this.sb_window)
		sb.draw()

def move_up():
	"""
	Decrements this.row_selector and redraws window, if necessary
	:return: (int) updated row_selector, or "empty" if empty table
	"""
	if len(this.table) == 0:
		return "empty"

	if this.row_selector > 0:
		this.row_selector -= 1
		this.draw()
	return this.table_uids[this.row_selector]


def move_down():
	"""
	Increments this.row_selector and redraws window, if necessary
	:return: (int) updated row_selector, or "empty" if empty table
	"""
	if len(this.table) == 0:
		return "empty"
		
	if this.row_selector < len(table)-1:
		this.row_selector += 1
		this.draw()
	return this.table_uids[this.row_selector]

def get_selected_row():
	"""
	Get selected row in table
	:return:
	"""
	return this.table[this.row_selector]

def get_column_widths(multiplier_list):
	"""
	Takes column width %s and returns list of corresponding col width raw values
	:param (List[float]) multiplier_list: the proportions that the total width should be divided into
	:return: (List[int]) the calculated widths of each column
	"""
	col_width_list = []
	for multiplier in multiplier_list:
		col_width_list.append(int(multiplier*this.tr_text_width))
	return col_width_list
Module that represents the left panel of the Skipper interactive terminal tool.   window________________________ |  __________________________  | | | bc_window (breadcrumbs)  | | | |__________________________| | |  __________________________  | | | th_window (table headers)| | | |__________________________| | |  __________________________  | | | table_window             | | | |  ______________________  | | | | |tr_window (table row) | | | | | |______________________| | | | |  ______________________  | | | | |tr_window (table row) | | | | | |______________________| | | | | ...                      | | | |                          | | | |                          | | | |                          | | | |                          | | | |__________________________| | |______________________________| # used to reference module variables # path to display in breadcrumbs window # resource types of items in the path # uids of items in path # column header names # width of each column # rows of data to be displayed in table window (curses.pad) # uids for each resource in the table # boolean (has children or not) for each resource in table # list of sev measures for each item in table # which row of the table should be highlighted # height of the breadcrumbs window # height of the search bar window # height of the table headers window # height of a table row window # height of the table window # absolute x,y of upper-left corner of left_window # width and height of left_window # absolute x,y of lower-right corner of left_window # relative x,y of upper-left corner of breadcrumbs window # relative x,y of upper-left corner of search bar window # relative x,y of upper-left corner of table headers window # absolute x,y of upper-left corner of table window (curses.pad) # _curses.window object that represents the left window # _curses.window object that represents the breadcrumbs window # _curses.window object that represents the search bar window # _curses.window object that represents that table headers window # _curses.pad object that represents that table window # starting row of table pad Initializes the left window and sets relevant position variables.  	:param (_curses.window) stdscr 	:param (int) height: Desired height of the left window. 	:param (int) width: Desired width of the left window. 	:param (int) y: Y-coordinate of upper-left corner of window. 	:param (int) x: X-coordinate of upper-left corner of window. 	:return: None # set x,y of upper-left corner of the left window # set width, height of the left window # set x,y of lower-right corner of the left window # set x,y of upper-left corner of breadcrumbs window Sets relevant content variables based on given arguments.  	:param (str) mode 	:param (List[str]) col_names: 					Names of the table column headers. 	:param (List[int]) col_widths: 					Widths of the table columns. 	:param (List[List[str]]) table: 					Rows of information to display in the table. 	:param (int) row_selector: 					Index of which row should be highlighted. 	:param (int) start_y: 					Starting row of table pad 	:param (List[str]) path_names: 					For app and cluster mode. List of resource names to display in breadcrumb window. 	:param (List[str]) path_rtypes: 					For app and cluster mode. List of resource types corresponding to the above resource names. 	:param (List[str]) path_uids: 					For app and cluster mode. List of resource uids corresponding to the above resource names. 	:param (List[str]) table_uids: 					List of uids for resources in table 	:param (List[bool]) has_children: 					List of booleans for knowing if resource has children 	:return: None # relative x,y inside left_window # absolute x,y # relative x,y inside left_window # absolute x,y # relative x,y inside left_window # absolute x,y Draws this.path_names and this.path_rtypes in the given breadcrumb window.  	:param (_curses.window) bc_window: _curses.window object that represents the breadcrumb window 	:return: None # edge case: path and rtypes are of different lengths # returns left and right padding needed to center str2 under str1 # edge case: name is shorter than resource type # solution: pad path names to always be longer than their corresponding type # create full path string # create full type string # edge case: path is longer than bc window width # solution: front-truncate the strings, if necessary Formats and draws row in the given window.  	:param (_curses.window)	tr_window: _curses.window object that represents the table row 	:return: None # edge case: start positions are too close together for the col_names # edge case: start positions are too large for width of window # edge case: col_names and start_postions have different lengths # pads and truncates the given string, as necessary, to fit col_width # generate a python format string based on col widths (this.col_widths) # draw the string inside the table row window # color status part of string Draws this.table inside the given curses.pad window.  	:param (_curses.pad) table_window: _curses.pad object that represents the table. 	:return: None # create a (derived) window for each row in this.table # Draw the row windows # Highlight the row at index this.row_selector # if there are any rows Populates this.window with content given by the most recent call to set_contents(...) # initialize and draw breadcrumbs derived window # only if Skipper is in app or cluster mode # nlines, ncols, rel_y, rel_x # initialize and draw table header window # nlines, ncols, rel_y, rel_x # initialize and draw table window (of type _curses.pad) # nlines, ncols, start_y, start_x # scroll down if we are at the bottom of the page # scroll up if we are at the top of the page # draw search bar last so cursor stays in the bar # nlines, ncols, rel_y, rel_x Decrements this.row_selector and redraws window, if necessary 	:return: (int) updated row_selector, or "empty" if empty table Increments this.row_selector and redraws window, if necessary 	:return: (int) updated row_selector, or "empty" if empty table Get selected row in table 	:return: Takes column width %s and returns list of corresponding col width raw values 	:param (List[float]) multiplier_list: the proportions that the total width should be divided into 	:return: (List[int]) the calculated widths of each column