¿Cómo crear un menú y submenús en curses Python?
python-2.7 (1)
AFAIK, no hay una extensión del menú de curses disponible en Python, por lo que tiene que lanzar su propia solución. Sé sobre este parche http://bugs.python.org/issue1723038 pero no sé cuál es el estado actual de este parche. Encontré una buena clase para Python que envuelve lo que quiero que se llame ''cmenu'' aquí http://www.promisc.org/blog/?p=33 pero también tengo un problema con eso. Quiero hacer un menú donde el usuario pueda elegir un elemento resaltado, pero en lugar de ejecutar una acción particular de inmediato, quiero mostrar otro menú, y luego tal vez otro, pedir alguna entrada, etc. Mi primer pensamiento fue eliminar el cmenu existente con screen.clear () o cleanup () pero el menú anterior no se elimina antes de que se dibuje el nuevo y el nuevo menú se parece a esto:
0. top
1. Exit
2. Another menu
-- end of the old menu that should go away --
3. first
4. second
5. third
No hay un método remove () para eliminar un elemento en cmenu (). Supongo que el hecho de que el menú anterior no se haya borrado está causado por el método ''while True'' loop in display (), pero cuando lo eliminé, algo extraño estaba sucediendo. Estoy usando Python 2.7, este es mi código actual:
#!/usr/bin/python
#
# Adapted from:
# http://blog.skeltonnetworks.com/2010/03/python-curses-custom-menu/
#
# Goncalo Gomes
# http://promisc.org
#
import signal
signal.signal(signal.SIGINT, signal.SIG_IGN)
import os
import sys
import curses
import traceback
import atexit
import time
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
class cmenu(object):
datum = {}
ordered = []
pos = 0
def __init__(self, options, title="python curses menu"):
curses.initscr()
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
curses.curs_set(0)
self.screen = curses.initscr()
self.screen.keypad(1)
self.h = curses.color_pair(1)
self.n = curses.A_NORMAL
for item in options:
k, v = item.items()[0]
self.datum[k] = v
self.ordered.append(k)
self.title = title
atexit.register(self.cleanup)
def cleanup(self):
curses.doupdate()
curses.endwin()
def upKey(self):
if self.pos == (len(self.ordered) - 1):
self.pos = 0
else:
self.pos += 1
def downKey(self):
if self.pos <= 0:
self.pos = len(self.ordered) - 1
else:
self.pos -= 1
def display(self):
screen = self.screen
while True:
screen.clear()
screen.addstr(2, 2, self.title, curses.A_STANDOUT|curses.A_BOLD)
screen.addstr(4, 2, "Please select an interface...", curses.A_BOLD)
ckey = None
func = None
while ckey != ord(''/n''):
for n in range(0, len(self.ordered)):
optn = self.ordered[n]
if n != self.pos:
screen.addstr(5 + n, 4, "%d. %s" % (n, optn), self.n)
else:
screen.addstr(5 + n, 4, "%d. %s" % (n, optn), self.h)
screen.refresh()
ckey = screen.getch()
if ckey == 258:
self.upKey()
if ckey == 259:
self.downKey()
ckey = 0
self.cleanup()
if self.pos >= 0 and self.pos < len(self.ordered):
self.datum[self.ordered[self.pos]]()
self.pos = -1
else:
curses.flash()
def top():
os.system("top")
def exit():
sys.exit(1)
def submenu():
# c.screen.clear() # nope
# c.cleanup() # nope
submenu_list = [{"first": exit}, {"second": exit}, {"third": exit}]
submenu = cmenu(submenu_list)
submenu.display()
try:
list = [{ "top": top }, {"Exit": exit}, {"Another menu": submenu}]
c = cmenu(list)
c.display()
except SystemExit:
pass
else:
#log(traceback.format_exc())
c.cleanup()
Realmente recomiendo mirar en el uso de panels . En cualquier momento en que tengas widgets que posiblemente puedan superponerse, esto hace que la vida sea mucho más fácil. Este es un ejemplo simple que debería comenzar. (Parece que ninguno de los curses.beep () o curses.flash () funciona en mi terminal, pero eso no viene al caso)
#!/usr/bin/env python2
import curses
from curses import panel
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(0,0)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
self.items.append((''exit'',''exit''))
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items)-1
def display(self):
self.panel.top()
self.panel.show()
self.window.clear()
while True:
self.window.refresh()
curses.doupdate()
for index, item in enumerate(self.items):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
msg = ''%d. %s'' % (index, item[0])
self.window.addstr(1+index, 1, msg, mode)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord(''/n'')]:
if self.position == len(self.items)-1:
break
else:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
self.window.clear()
self.panel.hide()
panel.update_panels()
curses.doupdate()
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(0)
submenu_items = [
(''beep'', curses.beep),
(''flash'', curses.flash)
]
submenu = Menu(submenu_items, self.screen)
main_menu_items = [
(''beep'', curses.beep),
(''flash'', curses.flash),
(''submenu'', submenu.display)
]
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == ''__main__'':
curses.wrapper(MyApp)
Algunas cosas a tener en cuenta al revisar su código.
Usar curses.wrapper (invocable) para iniciar su aplicación es más limpio que hacer su propio intento / excepto con la limpieza.
Su clase llama a initscr dos veces, lo que probablemente generará dos pantallas (no se ha probado si devuelve la misma pantalla si está configurada), y luego, cuando tiene varios menús, no hay un manejo adecuado de (lo que debería ser) diferentes ventanas / pantallas. Creo que es más claro y mejor llevar la contabilidad al menú, usar la pantalla y dejar que el menú haga una subventana para mostrar como en mi ejemplo.
Nombrar una lista "lista" no es una gran idea.
Si desea iniciar otra aplicación de terminal como ''top'', probablemente es mejor dejar que Python salga de forma limpia primero y luego iniciar para prevenir cualquier problema con la configuración de la terminal.