Menu navigable en Python avec ncurses
Le but de ce billet est de créer un petit menu navigable en Python avec la bibliothèque ncurses. Ce billet vient dans la ligné de Déplacer une fenêtre dans une console avec Python et ncurses, il est préférable de le lire avant si ce n’est déjà fait, comme ce billet contiendra moins d’explication.
Ce que l’on veut
Deux scripts sont proposé, le second en est une extention.
- Un menu complet, dans lequel nous pouvons nous déplacer à l’aide de flèche, et sélectionner l’un des éléments. L’élément du menu sera mis en surbrillance en choisissant une couleur différente (ici, il sera rouge). Il faudra s’assurer qu’on ne dépasse pas les limites du menu.
- Au lieu d’afficher tout le menu, seul quelques éléments seront affiché, mais la liste doit être navigable entièrement, on s’attend donc à voir apparaitre les éléments qui n’était pas affiché tout en se déplacent dans la liste. La navigation serra naturel.
Menu entièrement affiché
# -*- coding: utf-8 -*-
import curses
import curses.wrapper
class Menu(object):
menu = [
'item 0', 'item 1', 'item 2', 'item 3',
'item 4', 'item 5', 'item 6', 'item 7',
]
def __init__(self, scr):
self.scr = scr
self.init_curses()
self.item = { 'current': 0 }
self.draw_menu()
self.handle_keybinding()
def init_curses(self):
curses.curs_set(0)
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_RED, -1)
def draw_menu(self):
i = 2
for element in self.menu:
if element == self.get_current_item():
self.scr.addstr(i, 2, element, curses.color_pair(1))
else:
self.scr.addstr(i, 2, element)
i += 1
self.scr.refresh()
def get_current_item(self):
return self.menu[self.item['current']]
def navigate_up(self):
if self.item['current'] > 0:
self.item['current'] -= 1
def navigate_down(self):
if self.item['current'] < len(self.menu) -1:
self.item['current'] += 1
def show_result(self):
win = self.scr.subwin(5, 40, 2, 10)
win.border(0)
win.addstr(2, 2, 'Vous avez selectionné %s'
% self.menu[self.item['current']])
win.refresh()
win.getch()
win.erase()
def handle_keybinding(self):
while True:
ch = self.scr.getch()
if ch == curses.KEY_UP:
self.navigate_up()
elif ch == curses.KEY_DOWN:
self.navigate_down()
elif ch == curses.KEY_RIGHT:
self.show_result()
elif ch == ord('q'):
break
self.draw_menu()
if __name__ == '__main__':
curses.wrapper(Menu)
Il n’y a pas énormément besoin d’explication, si vous avez bien suivis l’autre billet, celui-ci doit aller tout seul.
Le choix de self.item = { 'current': 0 }
est surtout du à la suite du
problème, il n’est effectivement pas très justifié ici de prendre un
dictionnaire pour un seul élément.
Menu déroulant
# -*- coding: utf-8 -*-
import curses
import curses.wrapper
class Menu(object):
menu = [
'item 0', 'item 1', 'item 2', 'item 3',
'item 4', 'item 5', 'item 6', 'item 7',
]
def __init__(self, scr):
self.scr = scr
self.init_curses()
self.item = {
'current': 0,
'first': 0,
'show': 5,
}
self.draw_menu()
self.handle_keybinding()
def init_curses(self):
curses.curs_set(0)
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_RED, -1)
def draw_menu(self):
first = self.item['first']
last = self.item['first'] + self.item['show']
menu = self.menu[first:last]
i = 2
for element in menu:
if element == self.get_current_item():
self.scr.addstr(i, 2, element, curses.color_pair(1))
else:
self.scr.addstr(i, 2, element)
i += 1
self.scr.refresh()
def get_current_item(self):
return self.menu[self.item['current']]
def navigate_up(self):
if self.item['current'] > 0:
self.item['current'] -= 1
if self.item['current'] < self.item['first']:
self.item['first'] -= 1
def navigate_down(self):
if self.item['current'] < len(self.menu) -1:
self.item['current'] += 1
if self.item['current'] >= self.item['show'] + self.item['first']:
self.item['first'] += 1
def show_result(self):
win = self.scr.subwin(5, 40, 2, 10)
win.border(0)
win.addstr(2, 2, 'Vous avez selectionné %s'
% self.menu[self.item['current']])
win.refresh()
win.getch()
win.erase()
def handle_keybinding(self):
while True:
ch = self.scr.getch()
if ch == curses.KEY_UP:
self.navigate_up()
elif ch == curses.KEY_DOWN:
self.navigate_down()
elif ch == curses.KEY_RIGHT:
self.show_result()
elif ch == ord('q'):
break
self.draw_menu()
if __name__ == '__main__':
curses.wrapper(Menu)
La particularité est surtout de ne pas afficher tout le menu, il est ainsi découpé avant d’être affiché, ce n’était pas la seul possibilité d’aborder le problème.
last = self.item['first'] + self.item['show']
menu = self.menu[first:last]
L’autre point à faire attention, c’est de bien s’assurer que les méthodes de
navigation fassent ce qu’on attend. Par commodité, j’ai choisis un petit
dictionnaire item
pour repérer certains emplacement clé du menu, comme le
nombre d’élément affiché (show).
Un billet un peu plus cours en explication, mais j’espère utile pour se faire la main avec la bibliothèque curses de Python.