The curses library ( ncurses ) provides a terminal-independent method of controlling character screens. curses is a standard part of most UNIX-like systems (including Linux), and it has been ported to Windows and other systems. curses programs will run on plain-text systems, xterm, and other windowed console sessions, which makes these applications very portable. curses programs will run on plain text systems, xterm and other windowed console sessions, which makes these applications very portable.
Introduction to curses
Python's standard curses provide a basic interface to the public features of the "glass teletype" (which was called CRT in the 1970s, when the original curses library was first created). There are many ways to make interactive text-mode programs written in Python more clever. These methods fall into two categories.
On the one hand, there are Python modules that support the full set of features of ncurses (a superset of curses) or slang (a similar but separate console library). Most notably, one of these enhancement libraries (wrapped in the appropriate Python module) lets you add colors to the interface.
On the other hand, many of the advanced window widget libraries built on curses (or ncurses / slang) add features such as buttons, menus, scrollbars, and various public interface devices. If you've seen applications developed with libraries such as Borland's TurboWindows (for DOS), you know how attractive these features can be in a text-mode console. The functionality in the widget library can be achieved using curses alone, but it is also possible to take advantage of what other programmers have achieved with advanced interfaces. See the references for links to the modules mentioned.
This article deals only with the features of curses itself. Because the curses module is part of the standard distribution, you don't have to download the support libraries or other Python modules to find and use it (at least on Linux or UNIX systems). It's useful to understand the basic support provided by curses, even if it's only as a basis for understanding advanced modules. Even if you don't use other modules, it's easy to build nice and functional Python text-mode applications using curses alone. The pre-release notes mention that Python 2.0 will include an enhanced version of curses, but in any case, it should be compatible with the version described here.
application
I will be discussing the options for Txt2Html (in the"Lovely Python: my first web-based filtering agent"Txt2Html can be run in several ways, but for the sake of consistency with the purpose of this paper, we will look at Txt2Html run from the command line. However, in keeping with the purpose of this paper, we will look at Txt2Html run from the command line.One way to manipulate Txt2Html is to provide it with a set of command line variables (which describe aspects of the conversion to be performed) and then run the application as a batch process. For the occasional user, a more user-friendly interface provides an interactive selection screen that guides the user through the conversion options (providing visual feedback of the selected option) before performing the actual conversion.
The curses_txt2html interface is based on the common top bar menu with drop down and nested submenus. All menu-related functionality is designed "from scratch" on curses. Although these menus lack some of the features of the more complex curses wrappers, their basic functionality is implemented in a few lines of curses-only code. The interface also has a simple scrolling help box and a few user input fields. Below is a screen snapshot of the application showing the regular layout and style.
Applications on X terminals
Applications on Linux terminals
Packaging curses applications
The basic element of curses programming is the window object. A window is an area of the actual physical screen with an addressable cursor whose coordinates are associated with the window. Windows can be moved around and can be created and deleted without affecting other windows. In a window object, input or output operations occur at the cursor, which is usually set explicitly by the input or output method, but can be modified separately.
After curses is initialized, stream-oriented console input and output can be modified or completely disabled in a variety of ways. That's basically the whole point of using curses. However, once you've changed the streaming console interaction, if your program makes a mistake, you won't be able to display Python traceback events in the normal way; Andrew Kuchling solves this problem with a good top-level framework for curses programs (see thebibliography(in his tutorial).
The following template (which is essentially the same as Kuchling's) retains the error-reporting capabilities of normal command-line Python:
Top-level setup code for Python [curses] programs
import curses, traceback if __name__== '__main__': try : # Initialize curses stdscr=() # Turn off echoing of keys, and enter cbreak mode, # where no buffering is performed on keyboard input () () # In keypad mode, escape sequences for special keys # (like the cursor keys) will be interpreted and # a special value like curses.KEY_LEFT will be returned (1) main(stdscr) # Enter the main loop # Set everything back to normal (0) () () () # Terminate curses except : # In event of error, restore terminal to sane state. (0) () () () traceback.print_exc() # Print the exception
The try block performs some initialization, calls the main() function to do the actual work, and then performs the final cleanup. If something goes wrong, the except block restores the console to its default state and reports the exception encountered.
main() event loop
Now, let's examine the main() function and see what curses_txt2html does:
curses_txt2html.py main() function and event loop
defmain (stdscr): # Frame the interface area at fixed VT100 size global screen screen = (23, 79, 0, 0) () (2, 1, curses.ACS_HLINE, 77) () # Define the topbar menus file_menu = ( "File", "file_func()") proxy_menu = ( "Proxy Mode", "proxy_func()") doit_menu = ( "Do It!", "doit_func()") help_menu = ( "Help", "help_func()") exit_menu = ( "Exit", "EXIT") # Add the topbar menus to screen object topbar_menu((file_menu, proxy_menu, doit_menu, help_menu, exit_menu)) # Enter the topbar menu loop while topbar_key_handler(): draw_dict()
The main() function is easy to understand based on the three parts separated by blank lines.
The first part performs the general setup of the application's appearance. To establish predictable spacing between application elements, the interactive area is limited to the 80 x 25 VT100/PC screen size (even though the actual terminal window is larger). The program draws a box around this sub-window and uses a horizontal line to draw the visual offset of the top bar menu.
The second part builds the menus used by the application. The function topbar_menu() uses a few tricks to bind hotkeys to application actions and display menus with desired visual attributes. Get the source code archive (seebibliography ) to see all the code. topbar_menu() should be very common. (Feel free to incorporate it into your own application.) It is very important that once the hotkeys are bound, they eval() the string contained in the second element of the byte group associated with the menu. For example, activating the "File" menu in the above setup will call "eval("file_func()")". So the application is required to define a function called file_func(), which is required to return a Boolean value indicating whether the application termination state has been reached.
The third part is only two lines long, but that's the part where the whole application actually runs. The function topbar_key_handler() does what its name implies: it waits for keystrokes and then handles them. The keystroke handler may return a Boolean false value. (If it does, the application terminates.) The key handler in this application primarily checks the keys bound in the second paragraph. But even if your curses application binds keys differently than this application, you still want to use a similar event loop. The key handler will most likely use the following line of code:
c = ()# read a keypress
The call to draw_dict() is the only code in the event loop. This function draws values in several locations in the screen window. But in your application, you may want to include the following line of code:
() # redraw the screen w/ any new output
Add to the draw/refresh function (or just to the event loop itself).
Getting user input
The curses application gets all user input in the form of keystroke events. We've already looked at the .getch() method, now let's look at .getstr() as an example of combining .getch() with other input methods. Here's a shortened version of the file_func() function we mentioned before (it's activated by the "File" menu).
curses_txt2html.py file_func() function
deffile_func (): s = (5,10,2,1) () (1,2, "I", hotkey_attr) (1,3, "nput", menu_attr) (2,2, "O", hotkey_attr) (2,3, "utput", menu_attr) (3,2, "T", hotkey_attr) (3,3, "ype", menu_attr) (1,2, "", hotkey_attr) () c = () if c in (ord( 'I'), ord( 'i'), curses.KEY_ENTER, 10): () () (5,33, " "*43, curses.A_UNDERLINE) cfg_dict[ 'source'] = (5,33) () else : () () return CONTINUE
This function combines several curses features. The first thing it does is create another window object. Since this new window object is the actual drop-down menu for the "File" selection, the program draws a frame around it using the .box() method. In window s, the program draws several drop-down menu options. A slightly more labor-intensive method is used to highlight the hotkey for each option so that it contrasts with the rest of the option description. (See topbar_menu() in the full source code (see References) to learn a way to handle highlighting slightly more automatically.) The final .addstr() call moves the cursor to the default menu option. As with the main screen, () actually displays the elements drawn to the window object.
After drawing the drop-down menu, the program uses a simple () call to get the user's selections. In the demo application, the menu responds only to hotkeys, but not to arrow keys or moveable highlight bars. It is possible to build these more sophisticated menu functions by capturing additional key operations and setting up event loops in the drop-down menus. But this example is enough to illustrate the concept.
The program then compares the keystroke just read with the various hotkey values. In this case, both the case of the hotkey activates the drop-down menu options, and the ENTER key can be used to activate the default options. (The curses special key constants don't look entirely reliable, and I found it necessary to add the actual ASCII value "10" to capture the ENTER key.) Note that if you want to perform character-value comparisons, then wrap the string into the ord() built-in Python function.
When the "Input" option is checked, the program uses the .getstr() method, which provides field input with raw editing capabilities (backspace can be used). The input is terminated by the ENTER key, and the method returns the value entered. Usually this value is assigned to a variable as in the above example.
In order to visually differentiate the input fields, I used a little trick to preemptively add underscores to the areas where data entry will occur. This is necessary anyway, but it adds a visual effect. The underscore is drawn by the following line of code:
(5,33, " "*43, curses.A_UNDERLINE)
Of course, the program must also remove the underscores, which is done in the draw_dict() refresh function by the following line of code:
(5,33, " "*43, curses.A_NORMAL)
concluding remarks
The techniques outlined here as well as in the full application source code (see thebibliography The techniques used in this book should give you an initial introduction to curses programming. Use it to write your applications. It's not hard to use. The good news is that there are many languages other than Python that have access to the curses library, so what you've learned about using the Python curses module applies to other languages as well.
If the basic curses module does not meet your requirements, the "References" section provides links to a number of modules that add to the functionality of curses and provide excellent directions.