Как создать меню и подменю в проклятиях Python?

AFAIK, в Python отсутствует расширение меню curses, но вам нужно бросить свое собственное решение. Я знаю об этом патче http://bugs.python.org/issue1723038, но я не знаю, что это такое. Я нашел хороший класс для Python, который обертывает то, что я хочу назвать "cmenu" здесь http://www.promisc.org/blog/?p=33, но у меня также есть проблема с этим. Я хочу создать меню, в котором пользователь может выбрать выделенный элемент, но вместо того, чтобы сразу выполнить конкретное действие, я хочу отобразить другое меню, а затем, возможно, другое, попросить ввести некоторые данные и т.д. Моя первая мысль заключалась в том, чтобы удалить существующее cmenu с помощью screen.clear() или cleanup(), но старое меню не удаляется до того, как новый будет нарисован, и новое меню выглядит следующим образом:

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

Нет метода remove() для удаления элемента в cmenu(). Я думаю, факт, что старое меню не очищается, вызвано циклом "while True" в методе display(), но когда я удалил его, произошел какой-то странный материал. Я использую Python 2.7, это мой текущий код:

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

Я действительно рекомендую вам изучить панели . Каждый раз, когда у вас будут виджеты, которые могут пересекаться, это делает жизнь намного проще. Это простой пример, который должен вас начать. (Ни curses.beep(), ни curses.flash(), похоже, работают на моем терминале, но это не относится к точке)

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

Некоторые вещи, которые нужно учитывать при просмотре кода.

Использование curses.wrapper(вызываемого) для запуска вашего приложения более чистое, чем выполнение вашей собственной попытки/исключение с помощью очистки.

Ваш класс дважды вызывает initscr, который, вероятно, будет генерировать два экрана (не проверено, если он вернет тот же экран, если его настройка), а затем, когда у вас есть несколько меню, нет правильной обработки (что должно быть) разных окон/экранов, Я думаю, что его более ясная и лучшая бухгалтерская система передаст меню на экран, который будет использоваться, и позвольте меню сделать подокно для отображения, как в моем примере.

Именование списка "Список" - отличная идея.

Если вы хотите запустить другое приложение терминала, например "top", возможно, лучше сначала вывести cython python, а затем запустить, чтобы предотвратить любые попытки с помощью настроек терминала.