Simple Ncurses Bar Menu

By Frank Cox
(December 7, 2011)

It is often convenient to have a menu with a sliding highlight bar that the user can move up and down a list of items to review and select the item of interest.

This program was written on Centos 6 and requires ncurses, which is installed by default on most Linux distributions. You should be able to compile it as-is on pretty much any modern Linux or Unix system. I really don't know what it would take to get this stuff working on Microsoft Windows, though there does appear to be a MS Windows compatible version of ncurses called pdcurses which might work.

/* bar-menu.c 
Copyright (c) 2011, Frank Cox <theatre@melvilletheatre.com>
December 7, 2011
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY FRANK COX ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL FRANK COX BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE. 
 */

#include <ncurses.h>

int barmenu(const char **array, const int row, const int col, const int arraylength, const int width, int menulength, int selection);

int main(void)
        {
        int selection,row=1, col=10, arraylength=10, width=5, menulength=5;
        const char *testarray[]={"Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};
        initscr();
        noecho();
        keypad(stdscr,TRUE);
        
        selection=barmenu(testarray,row,col,arraylength,width,menulength,3);

        mvprintw(15,0,"Selection= %d",selection);
        refresh();
        getch();
        
        endwin();
        return 0;
        }
        
int barmenu(const char **array,const int row, const int col, const int arraylength, const int width, int menulength, int selection)
        {
        int counter,offset=0,ky=0;
        char formatstring[7];
        curs_set(0);

        if (arraylength < menulength)
                menulength=arraylength;

        if (selection > menulength)
                offset=selection-menulength+1;

        sprintf(formatstring,"%%-%ds",width); // remove - sign to right-justify the menu items
        
        while(ky != 27)
                {
                for (counter=0; counter < menulength; counter++)
                        {
                        if (counter+offset==selection)
                                attron(A_REVERSE);
                        mvprintw(row+counter,col,formatstring,array[counter+offset]);
                        attroff(A_REVERSE);
                        }
                
                ky=getch();
        
                switch(ky)
                        {
                        case KEY_UP:
                                if (selection)
                                        {
                                        selection--;
                                        if (selection < offset)
                                                offset--;
                                        }
                                break;
                        case KEY_DOWN:
                                if (selection < arraylength-1)
                                        {
                                        selection++;
                                        if (selection > offset+menulength-1)
                                                offset++;
                                        }
                                break;
                        case KEY_HOME:
                                selection=0;
                                offset=0;
                                break;
                        case KEY_END:
                                selection=arraylength-1;
                                offset=arraylength-menulength;
                                break;
                        case KEY_PPAGE:
                                selection-=menulength;
                                if (selection < 0)
                                        selection=0;
                                offset-=menulength;
                                if (offset < 0)
                                        offset=0;
                                break;
                        case KEY_NPAGE:
                                selection+=menulength;
                                if (selection > arraylength-1)
                                        selection=arraylength-1;
                                offset+=menulength;
                                if (offset > arraylength-menulength)
                                        offset=arraylength-menulength;
                                break;
                        case 10: //enter
                                return selection;
                                break;
                        case KEY_F(1): // function key 1
                                return -1;
                        case 27: //esc
                                // esc twice to get out, otherwise eat the chars that don't work
                                //from home or end on the keypad
                                ky=getch();
                                if (ky == 27)
                                        {
                                        curs_set(0);
                                        mvaddstr(9,77,"   ");
                                        return -1;
                                        }
                                else
                                        if (ky=='[')
                                                {
                                                getch();
                                                getch();
                                                }
                                        else 
                                                ungetch(ky);
                        }
                }
        return -1;
        }

Other articles written by Frank Cox can be found here.

Frank Cox owns and operates the Melville Theatre in Melville, Saskatchewan, Canada, and has been playing with computers for over 30 years.


This work is licensed under a Creative Commons Attribution-Share Alike 2.5 Canada License.