How to create a menu and submenus in Python curses?

天涯浪子 提交于 2019-12-03 04:24:53

问题


AFAIK, there is no curses menu extension available in Python yet so you have to roll your own solution. I know about this patch http://bugs.python.org/issue1723038 but I don't what's the current state of it. I found a nice class for Python that wraps what I want called 'cmenu' here http://www.promisc.org/blog/?p=33 but I have a problem with that too. I want to make a menu where user can choose a highlighted element but instead of executing a particular action right away I want to display another menu, and then maybe another, ask for some input etc. My first thought was to remove the existing cmenu with screen.clear() or cleanup() but the old menu is not removed before the new one is drawn and the new menu looks like this:

    0. top
    1. Exit
    2. Another menu
-- end of the old menu that should go away --
    3. first
    4. second
    5. third

There is no remove() method for removing an item in cmenu(). I guess the fact that the old menu is not cleared is caused by 'while True' loop in display() method but when I removed it some weird stuff was going on. I am using Python 2.7, this is my current code:

#!/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()

回答1:


I really recommend you look into using panels. Anytime you will have widgets that could possibly overlap, it makes life alot easier. This is a simple example that should get you started. (Neither curses.beep() or curses.flash() seem to work on my terminal, but that is beside the point)

#!/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)   

Some things to note when looking over your code.

Using curses.wrapper(callable) to launch your application is cleaner than doing your own try/except with cleanup.

Your class calls initscr twice which will probably generate two screens (havent tested if it returns the same screen if its setup), and then when you have multiple menus there is no proper handling of (what should be) different windows/screens. I think its clearer and better bookkeeping to pass the menu the screen to use and let the menu make a subwindow to display in as in my example.

Naming a list 'list' isnt a great idea.

If you want to launch another terminal app like 'top', it is probably better to let python exit curses cleanly first then launch in order to prevent any futzing with terminal settings.



来源:https://stackoverflow.com/questions/14200721/how-to-create-a-menu-and-submenus-in-python-curses

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!