208 lines
7.5 KiB
Python
208 lines
7.5 KiB
Python
import curses
|
|
from curses import wrapper
|
|
from curses.textpad import Textbox, rectangle
|
|
from enum import Enum
|
|
# doc: https://docs.python.org/3/howto/curses.html, https://docs.python.org/3/library/curses.html#module-curses.textpad
|
|
|
|
# color combos
|
|
CYAN_MAGENTA = 1
|
|
RED_BLACK = 2
|
|
MAGENTA_CYAN = 3
|
|
YELLOW_BLACK = 4
|
|
WHITE_BLUE = 5
|
|
CYAN_BLACK = 6
|
|
MAGENTA_BLACK = 7
|
|
WHITE_MAGENTA = 8
|
|
|
|
menu_width = 27
|
|
controls_lines = 5
|
|
|
|
|
|
"""
|
|
COLOR_BLACK
|
|
COLOR_RED
|
|
COLOR_GREEN
|
|
COLOR_YELLOW
|
|
COLOR_BLUE
|
|
COLOR_MAGENTA
|
|
COLOR_CYAN
|
|
COLOR_WHITE
|
|
"""
|
|
|
|
class Project:
|
|
def __init__(self, title: str):
|
|
self.title = title
|
|
self.tasks = []
|
|
|
|
def addTask(self, task):
|
|
self.tasks.append(task)
|
|
|
|
def removeTask(self, task):
|
|
self.tasks.remove(task)
|
|
|
|
class Task:
|
|
def __init__(self,title: str,desc=""):
|
|
self.title = title
|
|
self.status = Status.IDLE
|
|
|
|
class Status(Enum):
|
|
DONE = 1
|
|
WORKING = 2
|
|
IDLE = 3
|
|
|
|
# TODO maybe get rid of this enum and just use a number
|
|
class SelectedWindow(Enum):
|
|
PROJECTS = 1
|
|
TASKS = 2
|
|
DESCRIPTION = 3
|
|
|
|
def get_x_pos_center(text: str):
|
|
return curses.COLS // 2 - len(text) // 2
|
|
|
|
def draw_instructions(stdscr):
|
|
controls = "CONTROLS"
|
|
# draw line
|
|
h, w = stdscr.getmaxyx()
|
|
instructions_start = h - controls_lines-1
|
|
stdscr.hline(instructions_start,0,curses.ACS_HLINE,menu_width//2 - len(controls)//2,curses.color_pair(MAGENTA_BLACK))
|
|
stdscr.addstr(instructions_start,menu_width//2-len(controls)//2,controls,curses.color_pair(WHITE_MAGENTA))
|
|
stdscr.hline(instructions_start,menu_width//2+len(controls)//2,curses.ACS_HLINE,menu_width//2-len(controls)//2,curses.color_pair(MAGENTA_BLACK))
|
|
stdscr.addstr(instructions_start + 1, 0, "UP/DOWN - move selection")
|
|
stdscr.addstr(instructions_start + 2, 0, "LEFT/RIGHT - switch section")
|
|
|
|
def draw_menu(stdscr, projects: list, idx: int, selected_window):
|
|
# draw line
|
|
stdscr.vline(0, menu_width, curses.ACS_VLINE, stdscr.getmaxyx()[0] - 1, curses.color_pair(CYAN_BLACK))
|
|
|
|
# draw project title
|
|
title = "PROJECTS"
|
|
title_start = menu_width // 2 - len(title) // 2
|
|
stdscr.attron(curses.color_pair(YELLOW_BLACK) | curses.A_REVERSE)
|
|
stdscr.addstr(0,0," " * (title_start-1))
|
|
stdscr.addstr(0, title_start-1, title)
|
|
stdscr.addstr(0, len(title) + title_start - 1, " " * (menu_width - (len(title) + title_start)+1))
|
|
stdscr.attroff(curses.color_pair(YELLOW_BLACK) | curses.A_REVERSE)
|
|
|
|
# draw projects
|
|
y = 1
|
|
for project_index, project in enumerate(projects):
|
|
if project_index == idx and SelectedWindow(selected_window) == SelectedWindow.PROJECTS:
|
|
stdscr.addstr(y, 0, project.title,curses.A_REVERSE)
|
|
else:
|
|
stdscr.addstr(y, 0, project.title)
|
|
y = y + 1
|
|
|
|
def draw_tasks(stdscr, tasks, selected_window,idx):
|
|
h, w = stdscr.getmaxyx()
|
|
|
|
# draw middle devidor line
|
|
stdscr.vline(0, w // 2, curses.ACS_VLINE, h - 1, curses.color_pair(CYAN_BLACK))
|
|
|
|
# draw tasks title
|
|
title = "TASKS"
|
|
width = w//2 - menu_width
|
|
title_start = menu_width + (width//2 - len(title)//2)
|
|
stdscr.attron(curses.color_pair(YELLOW_BLACK) | curses.A_REVERSE)
|
|
stdscr.addstr(0, menu_width + 1, " " * (title_start - menu_width))
|
|
stdscr.addstr(0,title_start,title)
|
|
stdscr.addstr(0,title_start + len(title), " " * (w//2 - (title_start + len(title))))
|
|
stdscr.attroff(curses.color_pair(YELLOW_BLACK) | curses.A_REVERSE)
|
|
|
|
# draw task names
|
|
y = 1
|
|
for i, task in enumerate(tasks):
|
|
if i == idx and SelectedWindow(selected_window) == SelectedWindow.TASKS:
|
|
stdscr.addstr(y, menu_width + 1, task.title,curses.A_REVERSE)
|
|
else:
|
|
stdscr.addstr(y, menu_width + 1, task.title)
|
|
y = y + 1
|
|
|
|
|
|
def main(stdscr):
|
|
|
|
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_MAGENTA)
|
|
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
|
|
curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_CYAN)
|
|
curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK)
|
|
curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_GREEN)
|
|
curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK)
|
|
curses.init_pair(7, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
|
|
curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
|
|
|
|
k = 0 # input key
|
|
project_index = 0
|
|
task_index = 0
|
|
selected_window = 1
|
|
projects = []
|
|
test_project = Project("Test")
|
|
test_project.addTask(Task("testtask", "testdesc"))
|
|
test_project.addTask(Task("testtask2", "testdesc2"))
|
|
test_project.addTask(Task("testtask3", "testdesc3"))
|
|
projects.append(test_project)
|
|
test_project2 = Project("Test2")
|
|
test_project2.addTask(Task("yeet"))
|
|
test_project2.addTask(Task("yeet2"))
|
|
test_project2.addTask(Task("yeet3"))
|
|
projects.append(test_project2)
|
|
|
|
|
|
while (k != ord('q')):
|
|
if k == ord('e'):
|
|
editing = not editing
|
|
elif k == 450: # up key
|
|
# only move the projects selection if we're on that pane
|
|
if SelectedWindow(selected_window) == SelectedWindow.PROJECTS:
|
|
project_index = project_index - 1
|
|
if project_index < 0: project_index = len(projects) - 1
|
|
# only move the task selection if we're on that pane
|
|
elif SelectedWindow(selected_window) == SelectedWindow.TASKS:
|
|
task_index = task_index - 1
|
|
if task_index < 0: task_index = len(projects[project_index].tasks)-1
|
|
|
|
elif k == 456: # down key
|
|
# only move the projects selection if we're on that pane
|
|
if SelectedWindow(selected_window) == SelectedWindow.PROJECTS:
|
|
project_index = project_index + 1
|
|
if project_index > len(projects) - 1: project_index = 0
|
|
# only move the task selection if we're on that pane
|
|
elif SelectedWindow(selected_window) == SelectedWindow.TASKS:
|
|
task_index = task_index + 1
|
|
if task_index > len(projects[project_index].tasks) - 1: task_index = 0
|
|
|
|
elif k == 454: # right key
|
|
selected_window = selected_window + 1
|
|
if selected_window > len(SelectedWindow):
|
|
selected_window = 1
|
|
|
|
# set the task selection to the first for when we select a task next
|
|
# because the previous task we were on might have more tasks than this one,
|
|
# so we don't want the index to be out of bounds
|
|
if SelectedWindow(selected_window) == SelectedWindow.PROJECTS: task_index = 0
|
|
|
|
elif k == 452: # left key
|
|
selected_window = selected_window - 1
|
|
if selected_window < 1:
|
|
selected_window = 3
|
|
|
|
# set the task selection to the first for when we select a task next
|
|
# because the previous task we were on might have more tasks than this one,
|
|
# so we don't want the index to be out of bounds
|
|
if SelectedWindow(selected_window) == SelectedWindow.PROJECTS: task_index = 0
|
|
|
|
|
|
stdscr.clear()
|
|
draw_menu(stdscr, projects, project_index, selected_window)
|
|
draw_tasks(stdscr, projects[project_index].tasks, selected_window, task_index)
|
|
draw_instructions(stdscr)
|
|
|
|
# draw botton text
|
|
stdscr.addstr(stdscr.getmaxyx()[0]-1,0,"TPM by Sem van der Hoeven",)
|
|
k = stdscr.getch()
|
|
stdscr.refresh()
|
|
|
|
curses.endwin()
|
|
|
|
# wrapper already calls noecho() and cbreak() and stdcr.keypad(True)
|
|
# it also resets the settings upon closing or upon error
|
|
wrapper(main)
|