According to the Official Documentation,
The curses library provides terminal-independent screen drawing and keyboard processing for text-based terminals (terminals) such as VT100s, Linux consoles, and emulation terminals provided by various programs. The terminal supports various control codes for performing common operations such as moving the cursor, scrolling the screen, and erasing the area. Different types of terminals may use very different control codes and often have a peculiar habit.
It may be useful when you want to create a simple GUI or when you want to work with server data that can only be accessed with ssh. You can also display the contents of CSV and DB.
For example, suppose you have the following csv file.
ID | Prefecture | Capital | Population | Area | Density |
---|---|---|---|---|---|
1 | Aichi | Nagoya | 70,43,235 | 5,153.81 | 1,366 |
2 | Akita | Akita | 11,89,215 | 11,612.11 | 102 |
3 | Aomori | Aomori | 14,75,635 | 9,606.26 | 154 |
... | ... | ... | ... | ... | ... |
45 | Yamagata | Yamagata | 12,44,040 | 9,323.34 | 133 |
46 | Yamaguchi | Yamaguchi | 15,28,107 | 6,110.76 | 250 |
47 | Yamanashi | Kofu | 8,88,170 | 4,465.37 | 199 |
I would like to create a GUI that can display the contents on the terminal and select the prefecture to process.
main.py
# -*- coding: utf-8 -*-
import curses
import csv
from math import ceil
ROWS_PER_PAGE = 20
ENTER = ord( "\n" )
ESC = 27
DOWN = curses.KEY_DOWN
UP = curses.KEY_UP
class UI():
def __init__(self, header, rows):
super().__init__()
self.header = header
self.rows = rows
#Initialization
self.screen = curses.initscr()
curses.noecho()
curses.cbreak()
curses.start_color()
self.screen.keypad(1)
#Color settings
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN)
self.highlight_text = curses.color_pair(1) #Use the pair id in the line above
self.normal_text = curses.A_NORMAL
self.screen.border(0)
curses.curs_set(0)
self.rows_per_page = ROWS_PER_PAGE
self.total_rows = len(self.rows)
#Record the width of each column
self.widths = []
#Border drawing
self.tavnit = '|'
self.separator = '+'
for index, title in enumerate(self.header):
#Make the column title and the longest value of each row the width of the column
max_col_length = max([len(row[index]) for row in self.rows])
max_col_length = max(max_col_length, len(title))
self.widths.append(max_col_length)
#Border settings
for w in self.widths:
#It looks like this:
# | %-2s | %-10s | %-10s | %-11s | %-9s | %-7s |
self.tavnit += " %-"+"%ss |" % (w,)
#It looks like this:
# +----+------------+------------+-------------+-----------+---------+
self.separator += '-'*w + '--+'
self.total_pages = int(ceil(self.total_rows / self.rows_per_page))
self.position = 1
self.page = 1
#Message to be displayed
self.msg = 'Page: {}/{}'.format(self.page, self.total_pages)
def end(self):
curses.endwin()
def draw(self):
self.screen.erase()
#Show message at the top
self.screen.addstr(1, 2, self.msg, self.normal_text)
#Border on the table
self.screen.addstr(2, 2, self.separator, self.normal_text)
#Show header
self.screen.addstr(3, 2, self.tavnit % tuple(self.header), self.normal_text)
#Border between header and content
self.screen.addstr(4, 2, self.separator, self.normal_text)
#Draw every line
row_start = 1 + (self.rows_per_page * (self.page - 1))
row_end = self.rows_per_page + 1 + (self.rows_per_page * (self.page - 1))
for i in range(row_start, row_end):
if i >= self.total_rows + 1:
break
row_number = i + (self.rows_per_page * (self.page - 1))
#Highlight line
if (row_number == self.position + (self.rows_per_page * (self.page - 1))):
color = self.highlight_text
else:
color = self.normal_text
#Since there are 4 lines such as messages and borders above+4
draw_number = i - (self.rows_per_page * (self.page - 1)) + 4 #Since there are 4 lines such as messages and borders above
self.screen.addstr(draw_number , 2, self.tavnit % tuple(self.rows[i - 1]), color)
#Bottom border of the table,Since there are 4 lines such as messages and borders above+4
bottom = min(row_end, self.total_rows + 1) - (self.rows_per_page * (self.page - 1)) + 4
self.screen.addstr(bottom, 2, self.separator, self.normal_text)
self.screen.refresh()
def down(self):
if self.page == self.total_pages:
if self.position < self.total_rows:
self.position += 1
else:
if self.position < self.rows_per_page + (self.rows_per_page * (self.page - 1)):
self.position += 1
else:
self.page += 1
self.position = 1 + (self.rows_per_page * (self.page - 1))
self.msg = 'Page: {}/{}'.format(self.page, self.total_pages)
self.draw()
def up(self):
if self.page == 1:
if self.position > 1:
self.position -= 1
else:
if self.position > (1 + (self.rows_per_page * (self.page - 1))):
self.position -= 1
else:
self.page -= 1
self.position = self.rows_per_page + (self.rows_per_page * (self.page - 1))
self.msg = 'Page: {}/{}'.format(self.page, self.total_pages)
self.draw()
def esc(self):
self.end()
def enter(self):
#What you want to do here
prefecture_id = self.rows[self.position - 1][0]
prefecture = self.rows[self.position - 1][1]
self.msg = 'Page: {}/{} ({} {} was selected.)' \
.format(self.page, self.total_pages, prefecture_id, prefecture)
self.draw()
def loop(self):
#Detects the entered key
key = self.screen.getch()
while 1:
if key == ENTER:
self.enter()
elif key == ESC:
self.esc()
break
elif key == DOWN:
self.down()
elif key == UP:
self.up()
key = self.screen.getch()
if __name__ == '__main__':
with open('prefectures.csv') as f:
reader = csv.reader(f)
data = list(reader)
header = data[0]
rows = data[1:]
ui = UI(header, rows)
ui.draw()
ui.loop()
$ python main.py
The maximum number of lines on the page is 20.
You can scroll with ↑
and ↓
.
ʻEnter (return) will display the information of the corresponding prefecture in the message. Exit the GUI with ʻESC
.
Recommended Posts