Mullai Arasu
Mullai Arasu

Reputation: 47

Python curses not accept curses.KEY_UP as input

I need your help in understanding the mistake in my new menu based python program.

I have created a menu based python using the curses library. The program works fine with main menu options by accepting all the keys including special keys as input when getting user input via getch() but with a sub-menu list it fails to accept the special key as input.

   # Increment or Decrement
        elif c == curses.KEY_DOWN: # down arrow
            if self.option < len(self.submenu):
                self.option += 1
            else: self.option = 0
        elif c == curses.KEY_UP: # up arrow
            if self.option > 0:
                self.option -= 1
            else: self.option = len(self.submenu)

So above the is condition I am using to select the option but no input is sent to curses when I am using the UP/DOWN arrow keys

I am sharing my full code below

import curses, os, traceback, json


file1 = json.loads(open('/etc/ansible/org/json/tag_definition.json').read())
submenu = list(file1.keys())

cfg_dict = {'InstanceName': 'NONE',
            'Environment': 'NONE',
            'InstanceType':'t2.micro',
            'SystemOwner':'1',
        'LifeCycle':'NONE',
        'DeptName':'NONE',
        'Org':'NONE'}



class CursedMenu(object):
    '''A class which abstracts the horrors of building a curses-based menu system'''
    def __init__(self):
        '''Initialization'''
        self.screen = curses.initscr()
        curses.noecho()
        curses.cbreak()
        curses.start_color()
        self.screen.keypad(1)

        # Highlighted and Normal line definitions
        curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
        self.highlighted = curses.color_pair(1)
        self.normal = curses.A_NORMAL


    def show(self, options, title="Title", subtitle="Subtitle"):
        '''Draws a menu with the given parameters'''
        self.set_options(options)
        self.title = title.upper()
        self.subtitle = subtitle.upper()
        self.selected = 0
        self.draw_menu()


    def set_options(self, options):
        '''Validates that the last option is "Exit"'''
        if options[-1] is not 'Exit':
            options.append('Exit')
        self.options = options

    def set_submenu(self, submenu):
        '''Validates that the last option is "Exit"'''
        if submenu[-1] is not 'Exit':
            submenu.append('Exit')
        self.submenu = submenu

    def draw_dict(self):
        self.screen.addstr(8, 35, " "*43, curses.A_BOLD)
        self.screen.addstr(10, 35," "*43, curses.A_BOLD)
        self.screen.addstr(12, 35," "*43, curses.A_BOLD)
        self.screen.addstr(14, 35," "*43, curses.A_BOLD)
        self.screen.addstr(16, 35," "*43, curses.A_BOLD)
        self.screen.addstr(18, 35," "*43, curses.A_BOLD)
        self.screen.addstr(20, 35," "*43, curses.A_BOLD)
        self.screen.addstr(8, 35, cfg_dict['InstanceName'], curses.A_STANDOUT)
        self.screen.addstr(10, 35,cfg_dict['Environment'], curses.A_STANDOUT)
        self.screen.addstr(12, 35,cfg_dict['InstanceType'], curses.A_STANDOUT)
        self.screen.addstr(14, 35,cfg_dict['SystemOwner'], curses.A_STANDOUT)
        self.screen.addstr(16, 35,cfg_dict['LifeCycle'], curses.A_STANDOUT)
        self.screen.addstr(18, 35,cfg_dict['DeptName'], curses.A_STANDOUT)
        self.screen.addstr(20, 35,cfg_dict['Org'], curses.A_STANDOUT)
        self.screen.refresh()

    def draw_menu(self):
        '''Actually draws the menu and handles branching'''
        request = ""
        try:
            while request is not "Exit":
                self.draw()
                request = self.get_user_input()
                self.handle_request(request)
            self.__exit__()

        # Also calls __exit__, but adds traceback after
        except Exception as exception:
            self.__exit__()
            traceback.print_exc()


    def draw(self):
        '''Draw the menu and lines'''
        #self.screen.border(0)
        self.screen.subwin(40, 80, 0, 0).box()
        self.screen.addstr(2,30, self.title, curses.A_STANDOUT|curses.A_BOLD) # Title for this menu
        self.screen.hline(3, 1, curses.ACS_HLINE, 78)
        self.screen.addstr(4,2, self.subtitle, curses.A_BOLD) #Subtitle for this menu
        # Display all the menu items, showing the 'pos' item highlighted
        left = 2
        for index in range(len(self.options)):
            menu_name = len(self.options[index])
            textstyle = self.normal
            if index == self.selected:
                textstyle = self.highlighted
            self.screen.addstr(5, left, "%d.%s" % (index+1, self.options[index]), textstyle)
            left = left + menu_name + 3
        self.screen.addstr(8, 4, "                Instance Name:", curses.A_BOLD)
        self.screen.addstr(10, 4,"                  Environment:", curses.A_BOLD)
        self.screen.addstr(12, 4,"                Instance Type:", curses.A_BOLD)
        self.screen.addstr(14, 4,"                  SystemOwner:", curses.A_BOLD)
        self.screen.addstr(16, 4,"                    LifeCycle:", curses.A_BOLD)
        self.screen.addstr(18, 4,"              Department Name:", curses.A_BOLD)
        self.screen.addstr(20, 4,"                          Org:", curses.A_BOLD)
        self.draw_dict()
        self.screen.refresh()

    def get_user_input(self):
        '''Gets the user's input and acts appropriately'''
        user_in = self.screen.getch() # Gets user input

        '''Enter and Exit Keys are special cases'''
        if user_in == 10:
            return self.options[self.selected]
        if user_in == 27:
            return self.options[-1]
        if user_in == (curses.KEY_END, ord('!')):
            return self.options[-1]

        # This is a number; check to see if we can set it
        if user_in >= ord('1') and user_in <= ord(str(min(9,len(self.options)+1))):
            self.selected = user_in - ord('0') - 1 # convert keypress back to a number, then subtract 1 to get index
            return

        # Increment or Decrement
        if user_in == curses.KEY_LEFT: # left arrow
            self.selected -=1
        if user_in == curses.KEY_RIGHT: # right arrow
            self.selected +=1
        self.selected = self.selected % len(self.options)
        return



    def handle_request(self, request):
        '''This is where you do things with the request'''
        if request is "Org":
           self.org_func()
        if request is None: return

    def org_func(self):
        '''Actually draws the submenu and handles branching'''
        c = None
        self.option = 0
        self.set_submenu(submenu)
        height = len(self.submenu)
        while c != 10:
            self.s = curses.newwin(height+2, 20, 6, 14)
            self.s.box()
            for index in range(len(self.submenu)):
                textstyle = self.normal
                if index == self.option:
                    textstyle = self.highlighted
                self.s.addstr(index+1,1, "%d-%s" % (index+1, self.submenu[index]), textstyle)
            self.s.refresh()
            c = self.s.getch() # Gets user input
            if c == ord('k'):
                d = self.submenu[self.option]
                cfg_dict['Org'] = file1[d]['Org']
        # This is a number; check to see if we can set it
            if c >= ord('1') and c <= ord(str(len(self.submenu)+1)):
                self.option = c - ord('0') - 1 # convert keypress back to a number, then subtract 1 to get index
       # Increment or Decrement
            elif c == curses.KEY_DOWN: # down arrow
                if self.option < len(self.submenu):
                    self.option += 1
                else: self.option = 0
            elif c == curses.KEY_UP: # up arrow
                if self.option > 0:
                    self.option -= 1
                else: self.option = len(self.submenu)
        return self.option

    def __exit__(self):
        curses.endwin()
        os.system('clear')


'''demo'''
cm = CursedMenu()
cm.show(['Instance-ID','Org','Tag'], title='Instance Launch', subtitle='Options')

It works perfectly fine if I am using the alphabets and numerics as input but not with the special keys. I have used the code below and it was working

    elif c == ord('k'): # down arrow
        if self.option < len(self.submenu):
            self.option += 1
        else: self.option = 0
    elif c == ord('i'): # up arrow
        if self.option > 0:
            self.option -= 1
        else: self.option = len(self.submenu)

Please help understand why the code is not accepting the Special Keys.

Below is the input json I have used

{

   "Dept1": {

      "Instance Name": "instance1",

      "Environment": "environment1",

      "SystemOwner": "Owner1",

      "LifeCycle": "Lcy1",

      "DeptName": "Check1",

      "Org": "CSO"

   }
}

Upvotes: 1

Views: 2237

Answers (1)

Thomas Dickey
Thomas Dickey

Reputation: 54465

When you did

self.s = curses.newwin(height+2, 20, 6, 14)

you omitted setting properties for the new window, e.g,

self.s.keypad(1)

Upvotes: 3

Related Questions