Module cvtool.core.curses_toolbox
A list editor written in ncurses.
*** Use of this program requires attribution. ***
Author: Daniel Ellis github: @wolfiex email: curses_toolbox@danielellisresearch.com
Modification and Usage for CMIP granted by author: daniel.ellis@ext.esa.int
Expand source code
'''
A list editor written in ncurses.
*** Use of this program requires attribution. ***
Author: Daniel Ellis
github: @wolfiex
email: curses_toolbox@danielellisresearch.com
Modification and Usage for CMIP granted by author:
daniel.ellis@ext.esa.int
'''
import curses
def get_terminal_lines():
stdscr = curses.initscr()
num_lines, _ = stdscr.getmaxyx()
curses.endwin()
return num_lines
def reformat(items):
if isinstance( items, dict ): items = items.items()
if type(items) is type(dict().items()): items = [[*i] for i in items]
if isinstance(items,list) and not isinstance(items[0],list) : items = [[*i] for i in enumerate(items)]
# import pprint
# pprint.pprint(items)
return items
class CursesEditor:
def __init__(self, items, save_func=None, check=None, title=''):
self.items = reformat(items)
self.historic = self.items.copy()
self.save_func = save_func
self.check = check
self.selected_row = 0
self.title = title
def display_items(self, stdscr):
stdscr.clear()
title = f" {self.title} "
title_length = len(title)
title_start = (stdscr.getmaxyx()[1] - title_length) // 2
stdscr.attron(curses.color_pair(3)) # Apply the violet color pair
stdscr.addstr(0, title_start, title)
stdscr.attroff(curses.color_pair(3)) # Restore default color
for i, row in enumerate(self.items):
if i == self.selected_row:
stdscr.addstr(i + 2, 0, f"{row[0]} : {row[1]}", curses.color_pair(1))
else:
stdscr.addstr(i + 2, 0, f"{row[0]} : {row[1]}")
if self.selected_row == len(self.items):
stdscr.addstr(len(self.items) + 2, 0, "Save (s)", curses.color_pair(2) | curses.A_REVERSE)
else:
stdscr.addstr(len(self.items) + 2, 0, "Save (s)", curses.color_pair(2))
if self.selected_row == len(self.items) + 1:
stdscr.addstr(len(self.items) + 3, 0, "Quit (q)", curses.color_pair(2) | curses.A_REVERSE)
else:
stdscr.addstr(len(self.items) + 3, 0, "Quit (q)", curses.color_pair(2))
stdscr.refresh()
def edit_value(self, stdscr):
stdscr.move(self.selected_row + 2, len(self.items[self.selected_row][0]) + 2)
stdscr.clrtoeol()
curses.echo()
new_value = stdscr.getstr().decode()
valid = self.is_valid(self.items[self.selected_row][0] , new_value)
if not valid:
stdscr.addstr(len(self.items) +2, 0, "Invalid value !" , curses.A_BOLD)
stdscr.refresh()
stdscr.getch()
else:
self.items[self.selected_row][1] = new_value
curses.noecho()
self.check_value(stdscr)
def check_value(self, stdscr):
value = self.items[self.selected_row][1]
if value == 'invalid':
stdscr.addstr(len(self.items) + 2, 0, "Invalid value!", curses.A_BOLD)
else:
stdscr.addstr(len(self.items) + 2, 0, "Value is valid.", curses.A_BOLD)
stdscr.refresh()
def save_items(self):
if self.save_func is not None:
return self.save_func(self.items)
return False
def is_valid(self,key, value):
if self.check is None: return (True,"")
else:
# c = self.check(key,value)
# if c[0]:
# return c
# # change output to {status,msg}
return self.check(key,value)
def main(self, stdscr):
curses.curs_set(0)
stdscr.keypad(1)
curses.start_color()
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
while True:
self.display_items(stdscr)
key = stdscr.getch()
if key == curses.KEY_UP and self.selected_row > 0:
self.selected_row -= 1
elif key == curses.KEY_DOWN and self.selected_row < len(self.items) + 1:
self.selected_row += 1
elif key == curses.KEY_ENTER or key in [10, 113, 115, 110,101,114]:
#[ord('\n'), ord('q'), ord('s'), ord('n'), ord('e'),ord(r)]:
if self.selected_row == len(self.items) or key == ord('s'):
return self.save_items()
break
elif self.selected_row == len(self.items) + 1 or key == ord('q'):
return False
break
elif key == ord('r'):
curses.echo()
self.items[self.selected_row][1] = ''+self.historic[self.selected_row][1]
curses.noecho()
elif self.selected_row < len(self.items) or key == ord('e'):
self.edit_value(stdscr)
######
# fix revert
#######
stdscr.clear()
stdscr.refresh()
class CursesSelector:
def __init__(self, items, title=''):
self.items = reformat(items)
self.selected_row = 0
self.n = get_terminal_lines() - 6
self.title = title
def display_items(self, stdscr):
stdscr.clear()
start_index = self.selected_row - (self.selected_row % self.n)
end_index = min(start_index + self.n, len(self.items))
title = f" {self.title} "
title_length = len(title)
title_start = (stdscr.getmaxyx()[1] - title_length) // 2
stdscr.attron(curses.color_pair(3)) # Apply the violet color pair
stdscr.addstr(0, title_start, title)
stdscr.attroff(curses.color_pair(3)) # Restore default color
for i, row in enumerate(self.items[start_index:end_index], start=start_index):
if i == self.selected_row:
stdscr.addstr(i - start_index + 2, 0, f"{row[0]} : {row[1]}", curses.color_pair(1))
else:
stdscr.addstr(i - start_index + 2, 0, f"{row[0]} : {row[1]}")
if self.selected_row >= len(self.items):
stdscr.addstr(self.n + 2, 0, "Add New (n)", curses.color_pair(2) | curses.A_REVERSE)
else:
stdscr.addstr(self.n + 2, 0, "Add New (n)", curses.color_pair(2))
if self.selected_row == len(self.items) + 1:
stdscr.addstr(self.n + 3, 0, "Quit (q)", curses.color_pair(2) | curses.A_REVERSE)
else:
stdscr.addstr(self.n + 3, 0, "Quit (q)", curses.color_pair(2))
counter_text = f"{start_index + 1} - {min(start_index + self.n, len(self.items))} items out of {len(self.items)}"
stdscr.addstr(self.n + 4, 0, counter_text, curses.color_pair(2))
stdscr.refresh()
def select_value(self, stdscr):
key = stdscr.getch()
if key == curses.KEY_UP and self.selected_row > 0:
self.selected_row -= 1
elif key == curses.KEY_DOWN and self.selected_row < len(self.items) + 1:
self.selected_row += 1
elif key == curses.KEY_ENTER or key in [10,13,113, 115, 110]:
if self.selected_row == len(self.items) or key == ord('n'):
return 'new'
elif self.selected_row == len(self.items) + 1 or key == ord('q'):
return False
else:
value = list(self.items)[self.selected_row]
return value
def main(self, stdscr):
curses.curs_set(0)
stdscr.keypad(1)
curses.start_color()
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
while True:
self.display_items(stdscr)
result = self.select_value(stdscr)
if result is not None:
return result
stdscr.clear()
stdscr.refresh()
'''
# Example usage:
items = [
['Item 1', 'Value 1'],
['Item 2', 'Value 2'],
['Item 3', 'Value 3']
]
def valid(value):
if value == 'invalid':
return False
return True
def save_items():
# Custom save function
print("Custom save function called.")
editor = CursesEditor(items, save_func=save_items,check = valid)
curses.wrapper(editor.main)
'''
Functions
def get_terminal_lines()-
Expand source code
def get_terminal_lines(): stdscr = curses.initscr() num_lines, _ = stdscr.getmaxyx() curses.endwin() return num_lines def reformat(items)-
Expand source code
def reformat(items): if isinstance( items, dict ): items = items.items() if type(items) is type(dict().items()): items = [[*i] for i in items] if isinstance(items,list) and not isinstance(items[0],list) : items = [[*i] for i in enumerate(items)] # import pprint # pprint.pprint(items) return items
Classes
class CursesEditor (items, save_func=None, check=None, title='')-
Expand source code
class CursesEditor: def __init__(self, items, save_func=None, check=None, title=''): self.items = reformat(items) self.historic = self.items.copy() self.save_func = save_func self.check = check self.selected_row = 0 self.title = title def display_items(self, stdscr): stdscr.clear() title = f" {self.title} " title_length = len(title) title_start = (stdscr.getmaxyx()[1] - title_length) // 2 stdscr.attron(curses.color_pair(3)) # Apply the violet color pair stdscr.addstr(0, title_start, title) stdscr.attroff(curses.color_pair(3)) # Restore default color for i, row in enumerate(self.items): if i == self.selected_row: stdscr.addstr(i + 2, 0, f"{row[0]} : {row[1]}", curses.color_pair(1)) else: stdscr.addstr(i + 2, 0, f"{row[0]} : {row[1]}") if self.selected_row == len(self.items): stdscr.addstr(len(self.items) + 2, 0, "Save (s)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(len(self.items) + 2, 0, "Save (s)", curses.color_pair(2)) if self.selected_row == len(self.items) + 1: stdscr.addstr(len(self.items) + 3, 0, "Quit (q)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(len(self.items) + 3, 0, "Quit (q)", curses.color_pair(2)) stdscr.refresh() def edit_value(self, stdscr): stdscr.move(self.selected_row + 2, len(self.items[self.selected_row][0]) + 2) stdscr.clrtoeol() curses.echo() new_value = stdscr.getstr().decode() valid = self.is_valid(self.items[self.selected_row][0] , new_value) if not valid: stdscr.addstr(len(self.items) +2, 0, "Invalid value !" , curses.A_BOLD) stdscr.refresh() stdscr.getch() else: self.items[self.selected_row][1] = new_value curses.noecho() self.check_value(stdscr) def check_value(self, stdscr): value = self.items[self.selected_row][1] if value == 'invalid': stdscr.addstr(len(self.items) + 2, 0, "Invalid value!", curses.A_BOLD) else: stdscr.addstr(len(self.items) + 2, 0, "Value is valid.", curses.A_BOLD) stdscr.refresh() def save_items(self): if self.save_func is not None: return self.save_func(self.items) return False def is_valid(self,key, value): if self.check is None: return (True,"") else: # c = self.check(key,value) # if c[0]: # return c # # change output to {status,msg} return self.check(key,value) def main(self, stdscr): curses.curs_set(0) stdscr.keypad(1) curses.start_color() curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK) while True: self.display_items(stdscr) key = stdscr.getch() if key == curses.KEY_UP and self.selected_row > 0: self.selected_row -= 1 elif key == curses.KEY_DOWN and self.selected_row < len(self.items) + 1: self.selected_row += 1 elif key == curses.KEY_ENTER or key in [10, 113, 115, 110,101,114]: #[ord('\n'), ord('q'), ord('s'), ord('n'), ord('e'),ord(r)]: if self.selected_row == len(self.items) or key == ord('s'): return self.save_items() break elif self.selected_row == len(self.items) + 1 or key == ord('q'): return False break elif key == ord('r'): curses.echo() self.items[self.selected_row][1] = ''+self.historic[self.selected_row][1] curses.noecho() elif self.selected_row < len(self.items) or key == ord('e'): self.edit_value(stdscr) ###### # fix revert ####### stdscr.clear() stdscr.refresh()Methods
def check_value(self, stdscr)-
Expand source code
def check_value(self, stdscr): value = self.items[self.selected_row][1] if value == 'invalid': stdscr.addstr(len(self.items) + 2, 0, "Invalid value!", curses.A_BOLD) else: stdscr.addstr(len(self.items) + 2, 0, "Value is valid.", curses.A_BOLD) stdscr.refresh() def display_items(self, stdscr)-
Expand source code
def display_items(self, stdscr): stdscr.clear() title = f" {self.title} " title_length = len(title) title_start = (stdscr.getmaxyx()[1] - title_length) // 2 stdscr.attron(curses.color_pair(3)) # Apply the violet color pair stdscr.addstr(0, title_start, title) stdscr.attroff(curses.color_pair(3)) # Restore default color for i, row in enumerate(self.items): if i == self.selected_row: stdscr.addstr(i + 2, 0, f"{row[0]} : {row[1]}", curses.color_pair(1)) else: stdscr.addstr(i + 2, 0, f"{row[0]} : {row[1]}") if self.selected_row == len(self.items): stdscr.addstr(len(self.items) + 2, 0, "Save (s)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(len(self.items) + 2, 0, "Save (s)", curses.color_pair(2)) if self.selected_row == len(self.items) + 1: stdscr.addstr(len(self.items) + 3, 0, "Quit (q)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(len(self.items) + 3, 0, "Quit (q)", curses.color_pair(2)) stdscr.refresh() def edit_value(self, stdscr)-
Expand source code
def edit_value(self, stdscr): stdscr.move(self.selected_row + 2, len(self.items[self.selected_row][0]) + 2) stdscr.clrtoeol() curses.echo() new_value = stdscr.getstr().decode() valid = self.is_valid(self.items[self.selected_row][0] , new_value) if not valid: stdscr.addstr(len(self.items) +2, 0, "Invalid value !" , curses.A_BOLD) stdscr.refresh() stdscr.getch() else: self.items[self.selected_row][1] = new_value curses.noecho() self.check_value(stdscr) def is_valid(self, key, value)-
Expand source code
def is_valid(self,key, value): if self.check is None: return (True,"") else: # c = self.check(key,value) # if c[0]: # return c # # change output to {status,msg} return self.check(key,value) def main(self, stdscr)-
Expand source code
def main(self, stdscr): curses.curs_set(0) stdscr.keypad(1) curses.start_color() curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK) while True: self.display_items(stdscr) key = stdscr.getch() if key == curses.KEY_UP and self.selected_row > 0: self.selected_row -= 1 elif key == curses.KEY_DOWN and self.selected_row < len(self.items) + 1: self.selected_row += 1 elif key == curses.KEY_ENTER or key in [10, 113, 115, 110,101,114]: #[ord('\n'), ord('q'), ord('s'), ord('n'), ord('e'),ord(r)]: if self.selected_row == len(self.items) or key == ord('s'): return self.save_items() break elif self.selected_row == len(self.items) + 1 or key == ord('q'): return False break elif key == ord('r'): curses.echo() self.items[self.selected_row][1] = ''+self.historic[self.selected_row][1] curses.noecho() elif self.selected_row < len(self.items) or key == ord('e'): self.edit_value(stdscr) ###### # fix revert ####### stdscr.clear() stdscr.refresh() def save_items(self)-
Expand source code
def save_items(self): if self.save_func is not None: return self.save_func(self.items) return False
class CursesSelector (items, title='')-
Expand source code
class CursesSelector: def __init__(self, items, title=''): self.items = reformat(items) self.selected_row = 0 self.n = get_terminal_lines() - 6 self.title = title def display_items(self, stdscr): stdscr.clear() start_index = self.selected_row - (self.selected_row % self.n) end_index = min(start_index + self.n, len(self.items)) title = f" {self.title} " title_length = len(title) title_start = (stdscr.getmaxyx()[1] - title_length) // 2 stdscr.attron(curses.color_pair(3)) # Apply the violet color pair stdscr.addstr(0, title_start, title) stdscr.attroff(curses.color_pair(3)) # Restore default color for i, row in enumerate(self.items[start_index:end_index], start=start_index): if i == self.selected_row: stdscr.addstr(i - start_index + 2, 0, f"{row[0]} : {row[1]}", curses.color_pair(1)) else: stdscr.addstr(i - start_index + 2, 0, f"{row[0]} : {row[1]}") if self.selected_row >= len(self.items): stdscr.addstr(self.n + 2, 0, "Add New (n)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(self.n + 2, 0, "Add New (n)", curses.color_pair(2)) if self.selected_row == len(self.items) + 1: stdscr.addstr(self.n + 3, 0, "Quit (q)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(self.n + 3, 0, "Quit (q)", curses.color_pair(2)) counter_text = f"{start_index + 1} - {min(start_index + self.n, len(self.items))} items out of {len(self.items)}" stdscr.addstr(self.n + 4, 0, counter_text, curses.color_pair(2)) stdscr.refresh() def select_value(self, stdscr): key = stdscr.getch() if key == curses.KEY_UP and self.selected_row > 0: self.selected_row -= 1 elif key == curses.KEY_DOWN and self.selected_row < len(self.items) + 1: self.selected_row += 1 elif key == curses.KEY_ENTER or key in [10,13,113, 115, 110]: if self.selected_row == len(self.items) or key == ord('n'): return 'new' elif self.selected_row == len(self.items) + 1 or key == ord('q'): return False else: value = list(self.items)[self.selected_row] return value def main(self, stdscr): curses.curs_set(0) stdscr.keypad(1) curses.start_color() curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK) while True: self.display_items(stdscr) result = self.select_value(stdscr) if result is not None: return result stdscr.clear() stdscr.refresh()Methods
def display_items(self, stdscr)-
Expand source code
def display_items(self, stdscr): stdscr.clear() start_index = self.selected_row - (self.selected_row % self.n) end_index = min(start_index + self.n, len(self.items)) title = f" {self.title} " title_length = len(title) title_start = (stdscr.getmaxyx()[1] - title_length) // 2 stdscr.attron(curses.color_pair(3)) # Apply the violet color pair stdscr.addstr(0, title_start, title) stdscr.attroff(curses.color_pair(3)) # Restore default color for i, row in enumerate(self.items[start_index:end_index], start=start_index): if i == self.selected_row: stdscr.addstr(i - start_index + 2, 0, f"{row[0]} : {row[1]}", curses.color_pair(1)) else: stdscr.addstr(i - start_index + 2, 0, f"{row[0]} : {row[1]}") if self.selected_row >= len(self.items): stdscr.addstr(self.n + 2, 0, "Add New (n)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(self.n + 2, 0, "Add New (n)", curses.color_pair(2)) if self.selected_row == len(self.items) + 1: stdscr.addstr(self.n + 3, 0, "Quit (q)", curses.color_pair(2) | curses.A_REVERSE) else: stdscr.addstr(self.n + 3, 0, "Quit (q)", curses.color_pair(2)) counter_text = f"{start_index + 1} - {min(start_index + self.n, len(self.items))} items out of {len(self.items)}" stdscr.addstr(self.n + 4, 0, counter_text, curses.color_pair(2)) stdscr.refresh() def main(self, stdscr)-
Expand source code
def main(self, stdscr): curses.curs_set(0) stdscr.keypad(1) curses.start_color() curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK) while True: self.display_items(stdscr) result = self.select_value(stdscr) if result is not None: return result stdscr.clear() stdscr.refresh() def select_value(self, stdscr)-
Expand source code
def select_value(self, stdscr): key = stdscr.getch() if key == curses.KEY_UP and self.selected_row > 0: self.selected_row -= 1 elif key == curses.KEY_DOWN and self.selected_row < len(self.items) + 1: self.selected_row += 1 elif key == curses.KEY_ENTER or key in [10,13,113, 115, 110]: if self.selected_row == len(self.items) or key == ord('n'): return 'new' elif self.selected_row == len(self.items) + 1 or key == ord('q'): return False else: value = list(self.items)[self.selected_row] return value