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.

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.

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.

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.

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.

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:

Button(text)

Buttons are pretty simple. The text that you pass to the
button is what will be displayed on the button. There isn't
really a way to change the text later, so to do that, you
would have to create a new button.

CompactButton(text)

These are the same as buttons, but they don't have a nice
border. Use them when you are cramped for space on the
screen.

Checkbox(text, isOn = 0)

This produces a box that can be turned on or off. It is
independent of other checkboxes that you might have in the
form. The text is displayed adjacent to the box.
isOn is optional, and sets the initial value of the
box.

The Checkbox has the following methods:

value()

Returns whether the box is on or not.

selected()

Returns whether the box is currently selected or not.
This is different from whether or not the box is on as
returned by value().

setFlags(flag, sense)

setFlags is used to change whether or not a
user can actually select the checkbox. The possible
values of sense are FLAGS_SET,
FLAGS_RESET, and FLAGS_TOGGLE. FLAGS_SET
sets a particular flag to true. FLAGS_RESET sets the
flag to false. FLAGS_TOGGLE sets it to the opposite
value of what it currently is.

The only flag currently allowed is
FLAG_DISABLED. This flag sets whether or not
the checkbox can be selected, and thereby changed.

setValue()

Sets the current value of the checkbox.

SingleRadioButton(text, group, isOn = 0)

A Radio button is a widget, of which only one in a group can be
selected. group is the group of radio buttons to
which this button belongs. If there are no other buttons in
the group yet, then pass in None. If you have other
buttons, pass in one of the other buttons to associate the
buttons together.

A list box offers a list of selection. Only one thing can
be selected at a time. If you want to have multiple
selections, use a CheckboxTree.

height is how
many lines there should be in the box. scroll
determines whether or not a scroll bar is present.
returnExit says that if the ENTER key is pressed
while in the box, the form should be exited. width
is the number of characters wide the Listbox should be.
showCursor sets the status of the text cursor. In
general, you want to have it off because having it on is
fairly ugly.

There is quite a bit you can do with a list box.

append(text, item)

Adds an entry to the end of the Listbox. text
is whatever text you want to be displayed
in the list box. item is any object you would
like. You can pass in an integer, a string, or anything
at all.

insert(text, item, before)

Inserts an entry before another entry in the Listbox.
before is whatever object you passed in for
another item in the Listbox (not the display text). If
you pass in "None" for before, then this item will be
inserted at the beginning of the list.

delete(item)

Removes something from the Listbox. item is
whatever you passed in to the listbox before when you
added the element to the list.

replace(text, item)

Replaces another element with this element. The
element it replaces will be given by item. The new
element in the list will still return the same item.

current()

Returns the item corresponding to the current selection
in the Listbox. It does not return the text.

setCurrent(item)

Selects the entry in the Listbox given by
item.

clear()

Empties the Listbox.

Textbox(width, height, text, scroll = 0, wrap = 0)

A Textbox displays some text. width,
height, and text are fairly obvious. They
set the size of the box and the text that should be
displayed in it. scroll determines whether or not
there is a vertical scrollbar on the box. There is no such
thing as a horizontal scrollbar in snack, so you should
probably use wrap to keep everything inside the
box.

Textbox offers only one method. setText(text) lets
you change the text that will be displayed in the box.

This is a Textbox that uses reflow to
configure the text that will be displayed.

Scale(width, total)

Scales are different from other widgets in that they are
intended to be updated on the fly. This means that you must
use some other features of the Form and SnackScreen classes
that are not required by other widgets.

width is the character width of the scale on the screen.
total is the amount that the scale should be out
of.

The Scale class has only one method:

set(amount)

amount is a value between 0 and the
total parameter used to set up the Scale.

Here is an example of using a scale. Notice that instead
of running the form, the draw method is used instead. Also,
the screen must be refreshed every time that we want to see
what has happened.
screen = SnackScreen()
g = Grid(1, 1)
s = Scale(40, 1000)
s.set(0)
g.setField(s, 0, 0)
screen.gridWrappedWindow(g, "Scale Example")
f = Form()
f.add(s)
for i in range(1000):
for j in range(10000):
pass
s.set(i)
f.draw()
screen.refresh()
screen.finish()

If you had other components on the form, and you wanted to
get user input, then you would have to run the form at this
point to get the result of the user's input. However, the
scale would no longer be updated once the form was run.

width is the size of the field where text is
entered. text is the initial value of the entry.
hidden hides the text from view. The user can't
tell that anything is there, and when they edit, they can't
tell what they are typing. password displays the
value of the entry as a series of '*'. scroll
allows the user to scroll horizontally if the text of the
entry is too wide to fit in the space it has.
returnExit indicates that the containing Form
should exit when the ENTER key is pressed in the box.

Entry boxes have the following methods:

value()

This returns the text that is currently in the
Entry.

set(text)

Changes the text that is currently in the Entry.

setFlags(flag, sense)

setFlags is used to change whether or not a
user can actually select the Entry and change it's
value. The possible
values of sense are FLAGS_SET,
FLAGS_RESET, and FLAGS_TOGGLE. FLAGS_SET
sets a particular flag to true. FLAGS_RESET sets the
flag to false. FLAGS_TOGGLE sets it to the opposite
value of what it currently is.

The only flag currently allowed is
FLAG_DISABLED. This flag sets whether or not
the Entry can be selected, and thereby changed.

Useful Combination Classes

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

RadioBar(screen, buttonlist)

This class provides an easy way to set up a group of radio
buttons, instead of doing everything yourself.

buttonlist is a list of tuples containing
information about each button to be contained in the group.
Each tuple has three elements. The first is a title for the
button. The second element is an object to return if the
Radio button is currently selected. The third is either 0
or 1. If 1, it indicates that this button is the default.

RadioBar has the
following methods:

add(title, value, default = None)

This method creates a new Radio Button and adds it to the
group. value is an object associated with the
button that will be returned by getSelection

getSelection()

Returns the value associated with the Radio button that
is currently selected.

This class provides an easy way to set up a group of
buttons without having to do everything yourself. The
comments in snack.py suggest that when a ButtonBar is added
to a grid, the growx parameter be set.

buttonlist is a list of buttons that can take
three different types of button descriptions in the
list.

If the element is a tuple consisting of three elements,
then the elements are assumed to be the following:

The text to display in the button.

The value to return to the user when the
buttonPressed method is called.

A hotkey to be associated
with the button.

A tuple consisting of only two elements is the same as
for three elements, but without a hotkey.

A string is also acceptable. In this case, the
text for the button is set to the string. The value for
the button is set to the lower case value of the
string.

A ButtonBar has only one method:
buttonPressed(result). result is the return
value from calling Form.run(). If the Form exited because
of a hotkey mapped to one of the buttons, or because one of
the buttons was pressed, this function will return the value
associated with that button. Otherwise, it will return
None.

CheckboxTree(height, scroll = 0)

This class creates a container for a tree of Checkboxes.
An entry in the tree can either be a branch with checkboxes
and branches under it, or it can be a checkbox.

There are several methods associated with a CheckboxTree:

append(text, item = None, selected = 0)

This method adds an element to the end of the top level
of the CheckboxTree.

As usual, text is the display text for this
item. item is an object of your choice. It
should be unique for any elements that will be accessed
later by the class. If you don't supply an item, the
text will be used. selected indicates whether
the item is initially selected.

addItem(text, path, item = None, selected = 0)

This method allows you to add an item to the
CheckboxTree anywhere in the tree. The parameters are
the same as for the append method, except for the
path parameter.

path is a tuple of integers which indicates to
the CheckboxTree where this item should be placed in the
tree. The last element of the tuple should be
snackArgs['append'] to indicated that the tuple
is terminated. Here is an example of adding several things to a
CheckboxTree:
tree = CheckboxTree(height = 5, scroll = 1)
tree.addItem("First", (snackArgs['append'], ))
tree.addItem("Second", (snackArgs['append'], ))
tree.addItem("A", (0, snackArgs['append']))
tree.addItem("B", (0, snackArgs['append']))
tree.addItem("X", (0, 1, snackArgs['append']))

This would produce a tree with containing five
entries. Initially, only the items labeled
First and Second would be displayed.
Second would be a Checkbox, while First would
be expandable. If you expanded
First, you would see underneath it the
A, and B items. A would be a
checkbox, and B would be expandable. Finally, if you
expanded B, you would see a Checkbox for
X.

getCurrent()

Returns the item that the cursor is currently
highlighting.

getSelection()

Returns a list of all items that are currently
selected.

setEntry(item, text)

Changes the text associated with this particular
item.

setEntryValue(item, selected = 1)

Set whether or not the entry associated with this item
is selected.

getEntryValue(item)

Return whether or not the entry associated with this
item is selected.

Common Dialog Functions

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

This function displays a dialog with some descriptive text,
a listbox, and buttons at the bottom. It returns a tuple
consisting of the button
that was pressed to exit the dialog, and the entry in the
list that was selected. It's parameters are:

screen

A valid SnackScreen

title

The title for the dialog box.

text

Text that should be displayed at the top of the dialog
box before the Listbox.

items

This can be a list or tuple of things to display in the
Listbox. Each item in the list should be either a
string or a tuple. If it is a tuple, the first field is
the text to display, and the second is an item
associated with that text. If a string, then the text
to display is set to the string, and the item returned
will be the numeric position of the entry in the
Listbox.

This is a list of the prompts to be used as labels for
each Entry. A string is used as the label for the
Entry. On the other hand, if the prompt is a tuple,
then the first element of the tuple is used as the
tuple, and the second element is assumed to be an
already existing Entry.

allowCancel

This isn't actually used for anything in the version of
snack that I worked with.

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 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.