Quick Guide to Python's Snack Module

Written by Jared Riley

Snack is a python library based on newt that can be used to create a simple text based User interface. This is the package that was used by Red Hat to create their installation along with a number of configuration tools. It is an ideal platform upon which to create installation and configuration scripts, particularly if you don't want to rely on X, or you want to avoid complexity.

The newt RPM on Red Hat Linux comes with two sample python programs: peanuts.py, and popcorn.py. From these it is expected that the programmer will be able to figure out what to do. There is also an SGML guide to the newt C library. From this you can also make some inferences, but some things are different in the python world. You should read this document anyway because it gives an overview of how newt/snack programs work.

I have used snack to create a configuration program for software that will run on a server without X Windows installed. Along the way, I had to read the sample programs, Red Hat's installation program, and the C Library documentation. Hopefully the information here will help you do it with far less effort.

Other Resources

I have only documented what I have used. If you would like to undertake more complex programs or use features not discussed, here are some other places to look. Unless otherwise mentioned, these files are contained in the newt or newt-devel RPMS on Red Hat. Their location may be different on your version of Linux.

  1. snack.py

    This is the interface to the newt library. Whatever is in this file is what you are allowed to access from your program.

  2. tutorial.sgml

    This file documents some of the calls in the newt library for C. Not every call is documented, and not every call is available through snack.

    If you are like me and you can't figure out how to get something readable out of sgml in less than half a day, you can find it on the internet. I found it here.

  3. peanuts.py and popcorn.py

    These sample programs come with the newt-devel RPM on Red Hat. Between them, they use most of the widgets that are available in snack.

Loading Snack

To use the snack module, import it into your program. from snack import *

Snack provides the SnackScreen class, which you must allocate before you can do anything. screen = SnackScreen()

This call will paint your console a lovely blue. It will stay this way forever, unless you call the finish method. screen.finish()

Calling the finish method is very important. If you don't call it, then your screen will be left in a fairly unusable state when you exit python. To recover, you will have to call reset from the shell. You may also have to fix your interrupt and end of file keys (stty intr ^C; stty eof ^D).

Another caveat is that newt takes over your terminal and prevents signals due to keystrokes. ^Z and ^C will not work, so you should make an effort to provide a way to exit the program cleanly. There is a way to allow job control keystrokes to function correctly that will be discussed later.

Forms and Grids

You can't do very much with snack without using Forms and Grids. These two items have been combined into the GridFormHelp and GridForm classes, but I will leave those for you to figure out. Instead, I will show how to use basic Forms and Grids to create your display. In a later section I will show the types of Widgets that can be used.

screen = SnackScreen() upperleft = Widget() upperright = Widget() lowerleft = Widget() lowerright = Widget() g = Grid(2, 2) g.setField(upperleft, 0, 0, (0, 0, 1, 1)) g.setField(upperright, 1, 0) g.setField(lowerleft, 0, 1) g.setField(lowerright, 1, 1, growx = 1, growy = 1) screen.gridWrappedWindow(g, "Title Text") f = Form() f.add(g) result = f.run() screen.popWindow() screen.finish()

Grid

The call to grid sets the size of the grid. The width comes before the height, so in this case, the grid will be two widgets wide and 2 widgets high.

After creating the grid, the widgets are added into the grid using the setField method. The second and third parameters are the location in the grid where the widget is to be placed. Again, the horizontal parameter comes before the vertical setting. Since python is zero based, the available positions are 0 to horizontal size - 1.

snack will not complain if you put more than one widget into the same location in the grid. However, it will put all of the elements on top of each other, so you are unlikely to be pleased with the result.

In the first call to setField, I have used the optional padding argument. This is a tuple listing how much room is to be left around the widget in the grid. The order of the widget is (Left, Top, Right, Bottom).

The last call to setField has used the growx and growy parameters. These are used when you have different sized widgets. In order to make your window appear balanced, snack will add extra space into a smaller widget if it can so that the size of that widget will be grown to be the size of the widgets around it.

You may create a grid, and then put that grid into another grid's field. In this way you can create windows with different numbers of fields in different rows or columns.

Before the grid can be displayed on the screen, you must call screen.gridWrappedWindow(). This configures the grid and readies it for display.

Form

The Form is the thing that holds all of the widgets and displays them on the screen. It returns the widget that caused the form to be exited. Once the form has returned, you must call the screen.popWindow() method to remove the window from the screen.

You must add each widget to the form. However, there is a shortcut. If you have put all of your elements into a grid, then you can just add the grid. This will recursively add each element from the grid to the form.

reflow(text, width, flexDown = 5, flexUp = 5)

This is the only function in the snack library. reflow takes some text and reformats it to fit into the parameters given. flexDown and flexUp are guidelines to the library as to how many lines it should try to fit the text into while it is reconfiguring. reflow takes line breaks literally, so if you are going to use strings enclosed by """, then you should use line continuation characters to keep each paragraph together.

reflowreturns a tuple of the new wrapped text, the width of the text, and the actual height after adjustments.

Widget Classes

Here is a list of the different types of Widgets, and some of the parameters to the init function, and their methods:

Useful Combination Classes

There are several classes which have been defined for convenience because they are so common.

Common Dialog Functions

There are three functions that can be used to display common dialog boxes.

help

There are two ways to provide help to the user.

The first method is to display a single line of help on the bottom of the screen and is quite easy to do. This is done using the pushHelpLine and popHelpLine methods of the SnackScreen class. These calls must be bracketing. That is, each pushHelpLine must match with a call to popHelpLine.

A default help line is displayed if none is supplied, or if you try to set the help line to None. If you wish to remove the help line entirely, push a string with a space (" ").

The second method for providing help is to do something when the user presses <F1>. An example action for <F1> would be to bring up a dialog box with detailed explanations of the screen, and instructions on how to fill out the dialog box. To do this, you must create a function, and arrange for it to be called when the user presses <F1>. The helpCallback method of SnackScreen lets you set the help function.

Your help callback function should take two parameters (in addition to self). The first is the SnackScreen which is making the call. The second is an object which is used to indicate what screen the callback is coming from. Writing the actual function is fairly simple. Use the object to look up the help text that should be displayed in a dictionary and display it Alternatively, you could just put everything into the main object so that all the help callback has to do is display what it is given.

The next thing to do is arrange for the help callback to be called with the correct object at the right time. This is done when you create your Form. The Form class takes an option help parameter. It is this parameter that will be passed to the callback.

Hotkeys

Hotkeys are useful for providing your users with a way to navigate your dialog boxes without being forced to always tab to a button. If you have several widgets in a form, tabbing to the correct button quickly becomes tedious.

In general, <F1> is always defined as help, while <F12> is defined as exit the Form. The ButtonBar widget provides an easy way to use hot keys. Passing in a third element in a tuple for a button causes the hot key to be set to that element.

If you want to add a hot key to a Form, then you can use the addHotKey method. This method will be called automatically for any widgets with a hotkeys list as member of the class.

Available hot keys are defined in the hotkeys dictionary in the snack.py module. You can see there that the correct way to use a hot key is to pass in it's matching string.

If a hot key is pressed to exit the form, it will be the return value of running the form. You can check this directly, or pass it to your ButtonBar to find out the equivalent button.

It is actually possible to add your own hot keys to the hotkeys dictionary. import snack ESC = '^[' snack.hotkeys[ESC] = ord(ESC) snack.hotkeys[ord(ESC)] = ESC

In this example, ESC is set to the actual value of the escape key. You can produce this by typing Ctrl-V ESC in vi, or Ctrl-Q ESC in emacs. Note that the key is double mapped in the dictionary.


Jared Riley
Last modified: Mon Nov 11 16:36:10 EST 2002