Home>

There is a task for which I need to implement the interface in the console using Python. Target platform Win 7+. The task involves entering a text request from the user and accessing a certain site through the API in order to obtain a list of possible addresses corresponding to the request and then select one of the proposed options. According to the specification, the interface should be as user friendly as possible, so I want to implement control using the Up, Down, Enter, Escape keys. At the moment I found a library that solves my problems to the fullest, it's called npyscreen

As you can see, this library natively implements the back function to return to the previous menu. The library is based on one of the standard libraries called curses. The question is how to implement back from a submenu in the code below, I read the documentation for curses.panel, I tried to use the panel.bottom_panel() function to return the previous panel, but I get None. There are also several code options for creating menus based on curses, none of them could implement the back function. I want to understand how to do it. I've been scratching my head for a few days now. Menu code on npsycreen is here npyscreen menu.

The code in which I would like to implement the back function from the submenu.

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 hide(self):
        self.panel.hide()
        panel.update_panels()
        curses.doupdate()
    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.get()
            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),
                ('back',)
                ]
        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)
  • Answer # 1

    Your "exit" just works like "back". And the error is from the fact that there is no handler in the tuple with back. If you rewrite your enter processing, you get what you need

    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 hide(self):
            self.panel.hide()
            panel.update_panels()
            curses.doupdate()
        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.get()
                if key in [curses.KEY_ENTER, ord('\n')]:
                    if self.position== len(self.items)-1:
                        quit()
                        break
                    else:
                        if len(self.items[self.position])==1: # without handler
                            break
                        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= [
                    ('sub_beep', curses.beep),
                    ('sub_flash', curses.flash),
                    ('sub_flash2', curses.flash),
                    ('sub_flash3', curses.flash),
                    ('back',)
                    ]
            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)
    

    In the back tuple, I didn’t add a handler on purpose because I didn’t know what to write there. I tried to use your Enter press handler, when called from a tuple consisting of one element, I get a TypeError 'str' object is not callable from the line after the break, which, logically, should work only in the case of a tuple of 2 elements

    Timon552022-02-14 07:30:05

    in submenu_items, you must leave it like this ('back',) otherwise it considers that there is more than one element in the tuple and tries to execute the second element (which, as I understand it, you entered as a string)

    Sergey Tatarincev2022-02-14 07:54:41

    Yes, I really forgot about this feature of tuples. When running through PyCharm now I get 'Process finished with exit code -1073741819 (0xC0000005)' I have already encountered this error, nothing intelligibly could not figure out what it is and why. In the terminal, it quits the program completely, as if by quit, only with a delay of several seconds.

    Timon552022-02-14 08:27:48

    I added the full example in the answer.

    Sergey Tatarincev2022-02-14 09:22:01

    Absolutely identical behavior with completely your code

    Timon552022-02-14 09:50:25