curses を用いた TUI ダイアログの作成
呼称: curses によるダイアログメニュー
目的: TUI のダイアログメニューを作成する
特徴: ガリガリちから技で座標位置やキー入力を処理
用例: GUI のない環境でメニューを表示する
備考: dialog コマンドでも代用可
python で curses ライブラリを使用してメニューダイアログを作成してみました。
この程度であれば、普通に dialog コマンドを使用すれば良いです。しかし、何らかの理由で dialog が使用できない、又はこだわって全て python で書きたいときに有効です(^ ^;;
ターミナルの座標制御やキー入力の処理を簡潔に書けると、もう少し汎用的に使用できるかも?もう一工夫、必要ですね。
#!/usr/bin/env python """ Text User Interface with Curses """ import curses import sys menu_frame = """\ +----------------------------------------+ | This menu is sample for Curses | | select item which you want | +----------------------------------------+ | [ ]: 1st Item (return 'first') | | [ ]: 2nd Item (return 'second') | | [ ]: 3rd Item (return 'third') | | [ ]: 4th Item (return 'fourth') | | [ ]: 5th Item (return 'fifth') | | type "a": select/unselect all items | +----------------------------------------+ | < OK > < Quit > | +----------------------------------------+""" class CursesMenu: """ define terminal position """ def __init__(self): self.x_ast = 7 self.x_ok = 13 self.x_quit = 25 self.x_max = 44 self.y_hdr = 0 self.y_1st = 4 self.y_2nd = 5 self.y_3rd = 6 self.y_4th = 7 self.y_5th = 8 self.y_btn = 11 self.y_max = 12 self.item = {} def set_menu(self): self.scr.addstr(self.y_hdr, 0, menu_frame) def display_menu(self, scr): def ret_item(item, r=[]): l = item.items(); l.sort() for i in [x[0] for x in l if x[1] == '*']: if i == self.y_1st: r.append('first') elif i == self.y_2nd: r.append('second') elif i == self.y_3rd: r.append('third') elif i == self.y_4th: r.append('fourth') elif i == self.y_5th: r.append('fifth') return r def reverse_item(y, c='*'): if self.scr.instr(y, self.x_ast, 1) == '*': c=' ' self.scr.addch(y, self.x_ast, c) self.item[y] = c def reverse_all_item(c='*'): if self.scr.instr(self.y_1st, self.x_ast, 1) == '*': c = ' ' for y in [self.y_1st, self.y_2nd, self.y_3rd, self.y_4th, self.y_5th]: self.scr.addch(y, self.x_ast, c) self.item[y] = c def change_bgcolor(y, x): self.scr.addstr(self.y_btn, 0, ' | < OK > < Quit > |') if y == self.y_btn: if x == self.x_ok: self.scr.addstr(self.y_btn, self.x_ok, '< OK >', curses.color_pair(1)) elif x == self.x_quit: self.scr.addstr(self.y_btn, self.x_quit, '< Quit >', curses.color_pair(1)) def move_by_tab(): y, x = self.scr.getyx() if x == self.x_ast: y = self.y_btn x = self.x_ok elif x == self.x_ok: y = self.y_btn x = self.x_quit else: y = self.y_1st x = self.x_ast return y, x # clear the screen and set menu and keys self.scr = scr self.scr.clear() scr_y, scr_x = self.scr.getmaxyx() # check max terminal size if scr_y < self.y_max or scr_x < self.x_max: curses.endwin() print "Cannot display Menu in this size" sys.exit(0) # initialize color setting if enable if curses.has_colors(): curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # set initial cursor position cur_y = self.y_1st cur_x = self.x_ast self.set_menu() # set all items by default reverse_all_item() # get key input from user while True: # change color setting if enable if curses.has_colors(): change_bgcolor(cur_y, cur_x) self.scr.move(cur_y, cur_x) c = self.scr.getch() if 0 < c < 256: # normal character c = chr(c) if c in ' \n': y, x = self.scr.getyx() if x == self.x_ok: return ret_item(self.item) elif x == self.x_quit: sys.exit(0) elif x == self.x_ast: reverse_item(y) elif c in '\t': cur_y, cur_x = move_by_tab() elif c in 'aA': reverse_all_item() elif c in '1': reverse_item(self.y_1st) elif c in '2': reverse_item(self.y_2nd) elif c in '3': reverse_item(self.y_3rd) elif c in '4': reverse_item(self.y_4th) elif c in '5': reverse_item(self.y_5th) elif c in 'oO': cur_y, cur_x = self.y_btn, self.x_ok elif c in 'qQ': cur_y, cur_x = self.y_btn, self.x_quit else: pass # Ignore incorrect keys elif c == curses.KEY_UP and self.y_1st < cur_y: cur_x = self.x_ast if cur_y == self.y_btn: cur_y -= 2 cur_y -= 1 elif c == curses.KEY_DOWN and cur_y < self.y_btn: cur_x = self.x_ast if cur_y == self.y_5th: cur_x = self.x_ok cur_y += 2 cur_y += 1 elif c == curses.KEY_LEFT and cur_y == self.y_btn: cur_x = self.x_ok elif c == curses.KEY_RIGHT and cur_y == self.y_btn: cur_x = self.x_quit else: pass # Ignore incorrect keys """ initialize and call menu function """ def main(): item = curses.wrapper(CursesMenu().display_menu) print item if __name__ == '__main__': main()
実行結果。
+----------------------------------------+ | This menu is sample for Curses | | select item which you want | +----------------------------------------+ | [*]: 1st Item (return 'first') | | [ ]: 2nd Item (return 'second') | | [*]: 3rd Item (return 'third') | | [ ]: 4th Item (return 'fourth') | | [*]: 5th Item (return 'fifth') | | type "a": select/unselect all items | +----------------------------------------+ | < OK > < Quit > | +----------------------------------------+ OK を選択して Enter ['first', 'third', 'fifth']
リファレンス:
14.7 curses -- 文字セル表示のための端末操作
PythonにおけるCursesプログラミング